discourse_theme 0.8.0 → 0.9.1
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 +5 -1
- data/.rubocop.yml +1 -1
- data/.streerc +2 -0
- data/CHANGELOG.md +14 -0
- data/README.md +15 -0
- data/discourse_theme.gemspec +26 -24
- data/lib/discourse_theme/cli.rb +74 -27
- data/lib/discourse_theme/cli_commands/rspec.rb +246 -0
- data/lib/discourse_theme/client.rb +24 -3
- data/lib/discourse_theme/config.rb +13 -2
- data/lib/discourse_theme/downloader.rb +3 -6
- data/lib/discourse_theme/scaffold.rb +32 -30
- data/lib/discourse_theme/uploader.rb +7 -17
- data/lib/discourse_theme/version.rb +1 -1
- data/lib/discourse_theme/watcher.rb +27 -29
- data/lib/discourse_theme.rb +25 -24
- metadata +85 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea250fe17a63ff7c35463db99c6e18a8d19d64445f38a3e572b0897547f903d7
|
4
|
+
data.tar.gz: b3e938ab61da639be2d4e2fdb7928925c78374f9840548ec6ba1aaa1eedcc3da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d5cc920192553d7502846f375c6042e613c425eac2f9f20f578d99cbec9d75aa118944965a43a9710b861e3277d2becdbbaafc93e431de4bdbf34b55f95df0c
|
7
|
+
data.tar.gz: e2642e70c461e25d4c4343f0b2865b390db2424918d3339800b0226e8657f55ec17a6e083c72ba843058d9b3e9893d2cf8ae74b52f64672c02f8dcfc63e316ca
|
data/.github/workflows/ci.yml
CHANGED
@@ -13,7 +13,6 @@ jobs:
|
|
13
13
|
strategy:
|
14
14
|
matrix:
|
15
15
|
ruby:
|
16
|
-
- "2.7"
|
17
16
|
- "3.0"
|
18
17
|
- "3.1"
|
19
18
|
- "3.2"
|
@@ -30,6 +29,11 @@ jobs:
|
|
30
29
|
- name: Lint
|
31
30
|
run: bundle exec rubocop
|
32
31
|
|
32
|
+
- name: syntax_tree
|
33
|
+
run: |
|
34
|
+
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake') $(git ls-files '*.thor')
|
35
|
+
|
36
|
+
|
33
37
|
- name: Tests
|
34
38
|
run: bundle exec rake test
|
35
39
|
|
data/.rubocop.yml
CHANGED
data/.streerc
ADDED
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.9.1] - 2023-10-06
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- `rspec` command saving settings using wrong dir
|
13
|
+
|
14
|
+
## [0.9.0] - 2023-09-27
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
- Added the `rspec` command to the CLI to support running RSpec system tests for a theme using either a Docker container
|
19
|
+
running the `discourse/discourse_test` image or a local Discourse development environment. See 100f320847a22e11c145886588fac04479c143bb and
|
20
|
+
c0c920280bef7869f0515f5e4220cf5cd3e408ef for more details.
|
21
|
+
|
8
22
|
## [0.7.6] - 2023-09-16
|
9
23
|
|
10
24
|
### Fixed
|
data/README.md
CHANGED
@@ -36,6 +36,21 @@ Monitors a theme or component for changes. When changed the program will synchro
|
|
36
36
|
|
37
37
|
Uploads a theme to the server. Requires the theme to have been previously synchronized via `watch`.
|
38
38
|
|
39
|
+
### `discourse_theme rspec PATH`
|
40
|
+
|
41
|
+
Runs the [RSpec](https://rspec.info/) system tests under the `spec` folder in the designated theme directory.
|
42
|
+
|
43
|
+
On the first run for the given directory, you will be asked if you'll like to use a local Discourse repository to run the tests.
|
44
|
+
|
45
|
+
If you select 'Y' and proceeds to configure the path to the local Discourse repository, the tests will be ran using the local Discourse development environment provided by the local Discourse repository. Note that you'll have to set up the local test environment before
|
46
|
+
the tests can be ran successfully.
|
47
|
+
|
48
|
+
If the 'n' option is selected, the tests will run in a Docker container created using the [`discourse/discours_test:release`](https://hub.docker.com/r/discourse/discourse_test) Docker image. Note that this requires [Docker](https://docs.docker.com/engine/install/) to be installed.
|
49
|
+
|
50
|
+
When the `--headless` option is used, a local installation of the [Google Chrome browser](https://www.google.com/chrome/) is required.
|
51
|
+
|
52
|
+
Run `discourse_theme --help` for more usage details.
|
53
|
+
|
39
54
|
## Contributing
|
40
55
|
|
41
56
|
Bug reports and pull requests are welcome at [Meta Discourse](https://meta.discourse.org). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/discourse_theme.gemspec
CHANGED
@@ -5,39 +5,41 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require "discourse_theme/version"
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
|
-
spec.name
|
9
|
-
spec.version
|
10
|
-
spec.authors
|
11
|
-
spec.email
|
12
|
-
|
13
|
-
spec.summary
|
14
|
-
spec.description
|
15
|
-
spec.homepage
|
16
|
-
spec.license
|
17
|
-
|
18
|
-
spec.files
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
spec.bindir = "bin"
|
23
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
8
|
+
spec.name = "discourse_theme"
|
9
|
+
spec.version = DiscourseTheme::VERSION
|
10
|
+
spec.authors = ["Sam Saffron"]
|
11
|
+
spec.email = ["sam.saffron@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = "CLI helper for creating Discourse themes"
|
14
|
+
spec.description = "CLI helper for creating Discourse themes"
|
15
|
+
spec.homepage = "https://github.com/discourse/discourse_theme"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
|
20
|
+
spec.bindir = "bin"
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
24
22
|
spec.require_paths = ["lib"]
|
25
23
|
|
26
|
-
spec.required_ruby_version =
|
24
|
+
spec.required_ruby_version = ">= 3.0.0"
|
27
25
|
|
28
26
|
spec.add_runtime_dependency "minitar", "~> 0.6"
|
29
27
|
spec.add_runtime_dependency "listen", "~> 3.1"
|
30
28
|
spec.add_runtime_dependency "multipart-post", "~> 2.0"
|
31
29
|
spec.add_runtime_dependency "tty-prompt", "~> 0.18"
|
32
30
|
spec.add_runtime_dependency "rubyzip", "~> 1.2"
|
33
|
-
|
34
|
-
|
35
|
-
spec.add_development_dependency "
|
36
|
-
spec.add_development_dependency "
|
37
|
-
spec.add_development_dependency "
|
38
|
-
spec.add_development_dependency "guard
|
39
|
-
spec.add_development_dependency "
|
31
|
+
spec.add_runtime_dependency "selenium-webdriver", "> 4.11"
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler"
|
34
|
+
spec.add_development_dependency "rake"
|
35
|
+
spec.add_development_dependency "minitest"
|
36
|
+
spec.add_development_dependency "guard"
|
37
|
+
spec.add_development_dependency "guard-minitest"
|
38
|
+
spec.add_development_dependency "webmock"
|
40
39
|
spec.add_development_dependency "rubocop"
|
41
40
|
spec.add_development_dependency "rubocop-discourse"
|
42
41
|
spec.add_development_dependency "m"
|
42
|
+
spec.add_development_dependency "syntax_tree"
|
43
|
+
spec.add_development_dependency "syntax_tree-disable_ternary"
|
44
|
+
spec.add_development_dependency "mocha"
|
43
45
|
end
|
data/lib/discourse_theme/cli.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "cli_commands/rspec"
|
2
4
|
module DiscourseTheme
|
3
5
|
class Cli
|
4
6
|
@@cli_settings_filename = File.expand_path("~/.discourse_theme")
|
@@ -12,14 +14,25 @@ module DiscourseTheme
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def usage
|
15
|
-
puts
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
puts <<~USAGE
|
18
|
+
Usage: discourse_theme COMMAND [DIR] [OPTIONS]
|
19
|
+
|
20
|
+
Commands:
|
21
|
+
new DIR - Creates a new theme in the specified directory.
|
22
|
+
download DIR - Downloads a theme from the server and stores it in the specified directory.
|
23
|
+
upload DIR - Uploads the theme from the specified directory to Discourse.
|
24
|
+
watch DIR - Watches the theme in the specified directory and synchronizes any changes with Discourse.
|
25
|
+
rspec DIR [OPTIONS] - Runs the RSpec tests in the specified directory. The tests can be run using a local Discourse repository or a Docker container.
|
26
|
+
--headless - Runs the RSpec system type tests in headless mode. Applies to both modes.
|
27
|
+
|
28
|
+
If specified directory has been configured to run in a Docker container, the additional options are supported.
|
29
|
+
--rebuild - Forces a rebuilds of Docker container.
|
30
|
+
--verbose - Runs the command to prepare the Docker container in verbose mode.
|
31
|
+
|
32
|
+
Global Options:
|
33
|
+
--reset - Resets the configuration for the specified directory.
|
34
|
+
USAGE
|
35
|
+
|
23
36
|
exit 1
|
24
37
|
end
|
25
38
|
|
@@ -38,7 +51,9 @@ module DiscourseTheme
|
|
38
51
|
components = settings.components
|
39
52
|
|
40
53
|
if command == "new"
|
41
|
-
|
54
|
+
if Dir.exist?(dir) && !Dir.empty?(dir)
|
55
|
+
raise DiscourseTheme::ThemeError.new "'#{dir}' is not empty"
|
56
|
+
end
|
42
57
|
raise DiscourseTheme::ThemeError.new "git is not installed" if !command?("git")
|
43
58
|
raise DiscourseTheme::ThemeError.new "yarn is not installed" if !command?("yarn")
|
44
59
|
|
@@ -57,18 +72,23 @@ module DiscourseTheme
|
|
57
72
|
options["Create and sync with a new theme"] = :create
|
58
73
|
options["Select a different theme"] = :select
|
59
74
|
|
60
|
-
choice = UI.select(
|
75
|
+
choice = UI.select("How would you like to sync this theme?", options.keys)
|
61
76
|
|
62
77
|
if options[choice] == :create
|
63
78
|
theme_id = nil
|
64
79
|
elsif options[choice] == :select
|
65
80
|
themes = render_theme_list(theme_list)
|
66
|
-
choice = UI.select(
|
81
|
+
choice = UI.select("Which theme would you like to sync with?", themes)
|
67
82
|
theme_id = extract_theme_id(choice)
|
68
83
|
theme = theme_list.find { |t| t["id"] == theme_id }
|
69
84
|
end
|
70
85
|
|
71
|
-
about_json =
|
86
|
+
about_json =
|
87
|
+
begin
|
88
|
+
JSON.parse(File.read(File.join(dir, "about.json")))
|
89
|
+
rescue StandardError
|
90
|
+
nil
|
91
|
+
end
|
72
92
|
already_uploaded = !!theme
|
73
93
|
is_component = theme&.[]("component")
|
74
94
|
component_count = about_json&.[]("components")&.length || 0
|
@@ -78,11 +98,17 @@ module DiscourseTheme
|
|
78
98
|
options["Yes"] = :sync
|
79
99
|
options["No"] = :none
|
80
100
|
options = options.sort_by { |_, b| b == components.to_sym ? 0 : 1 }.to_h if components
|
81
|
-
choice = UI.select(
|
101
|
+
choice = UI.select("Would you like to update child theme components?", options.keys)
|
82
102
|
settings.components = components = options[choice].to_s
|
83
103
|
end
|
84
104
|
|
85
|
-
uploader =
|
105
|
+
uploader =
|
106
|
+
DiscourseTheme::Uploader.new(
|
107
|
+
dir: dir,
|
108
|
+
client: client,
|
109
|
+
theme_id: theme_id,
|
110
|
+
components: components,
|
111
|
+
)
|
86
112
|
|
87
113
|
UI.progress "Uploading theme from #{dir}"
|
88
114
|
settings.theme_id = theme_id = uploader.upload_full_theme
|
@@ -111,7 +137,7 @@ module DiscourseTheme
|
|
111
137
|
UI.progress "Loading theme list..."
|
112
138
|
themes = render_theme_list(client.get_themes_list)
|
113
139
|
|
114
|
-
choice = UI.select(
|
140
|
+
choice = UI.select("Which theme would you like to download?", themes)
|
115
141
|
theme_id = extract_theme_id(choice)
|
116
142
|
|
117
143
|
UI.progress "Downloading theme into #{dir}"
|
@@ -124,15 +150,25 @@ module DiscourseTheme
|
|
124
150
|
watch_theme?(args)
|
125
151
|
elsif command == "upload"
|
126
152
|
raise DiscourseTheme::ThemeError.new "'#{dir} does not exist" unless Dir.exist?(dir)
|
127
|
-
|
153
|
+
if theme_id == 0
|
154
|
+
raise DiscourseTheme::ThemeError.new "No theme_id is set, please sync via the 'watch' command initially"
|
155
|
+
end
|
128
156
|
client = DiscourseTheme::Client.new(dir, settings, reset: reset)
|
129
157
|
|
130
158
|
theme_list = client.get_themes_list
|
131
159
|
|
132
160
|
theme = theme_list.find { |t| t["id"] == theme_id }
|
133
|
-
|
161
|
+
unless theme
|
162
|
+
raise DiscourseTheme::ThemeError.new "theme_id is set, but the theme does not exist in Discourse"
|
163
|
+
end
|
134
164
|
|
135
|
-
uploader =
|
165
|
+
uploader =
|
166
|
+
DiscourseTheme::Uploader.new(
|
167
|
+
dir: dir,
|
168
|
+
client: client,
|
169
|
+
theme_id: theme_id,
|
170
|
+
components: components,
|
171
|
+
)
|
136
172
|
|
137
173
|
UI.progress "Uploading theme (id:#{theme_id}) from #{dir} "
|
138
174
|
settings.theme_id = theme_id = uploader.upload_full_theme
|
@@ -144,6 +180,13 @@ module DiscourseTheme
|
|
144
180
|
else
|
145
181
|
UI.info "Manage: #{client.root}/admin/customize/themes/#{theme_id}"
|
146
182
|
end
|
183
|
+
elsif command == "rspec"
|
184
|
+
DiscourseTheme::CliCommands::Rspec.run(
|
185
|
+
settings: config[dir.split("/spec")[0]],
|
186
|
+
dir: dir,
|
187
|
+
args: args,
|
188
|
+
reset: reset,
|
189
|
+
)
|
147
190
|
else
|
148
191
|
usage
|
149
192
|
end
|
@@ -158,13 +201,15 @@ module DiscourseTheme
|
|
158
201
|
private
|
159
202
|
|
160
203
|
def command?(cmd)
|
161
|
-
exts = ENV[
|
162
|
-
ENV[
|
163
|
-
|
164
|
-
|
165
|
-
|
204
|
+
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
|
205
|
+
ENV["PATH"]
|
206
|
+
.split(File::PATH_SEPARATOR)
|
207
|
+
.each do |path|
|
208
|
+
exts.each do |ext|
|
209
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
210
|
+
return true if File.executable?(exe) && !File.directory?(exe)
|
211
|
+
end
|
166
212
|
end
|
167
|
-
end
|
168
213
|
|
169
214
|
false
|
170
215
|
end
|
@@ -172,14 +217,16 @@ module DiscourseTheme
|
|
172
217
|
def watch_theme?(args)
|
173
218
|
if UI.yes?("Would you like to start 'watching' this theme?")
|
174
219
|
args[0] = "watch"
|
175
|
-
UI.progress "Running discourse_theme #{args.join(
|
220
|
+
UI.progress "Running discourse_theme #{args.join(" ")}"
|
176
221
|
run(args)
|
177
222
|
end
|
178
223
|
end
|
179
224
|
|
180
225
|
def render_theme_list(themes)
|
181
|
-
themes
|
182
|
-
.
|
226
|
+
themes
|
227
|
+
.sort_by { |t| t["updated_at"] }
|
228
|
+
.reverse
|
229
|
+
.map { |theme| "#{theme["name"]} (id:#{theme["id"]})" }
|
183
230
|
end
|
184
231
|
|
185
232
|
def extract_theme_id(rendered_name)
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "selenium-webdriver"
|
4
|
+
|
5
|
+
module DiscourseTheme
|
6
|
+
module CliCommands
|
7
|
+
class Rspec
|
8
|
+
DISCOURSE_TEST_DOCKER_CONTAINER_NAME_PREFIX = "discourse_theme_test"
|
9
|
+
DISCOURSE_THEME_TEST_TMP_DIR = "/tmp/.discourse_theme_test"
|
10
|
+
SELENIUM_HEADLESS_ENV = "SELENIUM_HEADLESS=0"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def discourse_test_docker_container_name
|
14
|
+
"#{DISCOURSE_TEST_DOCKER_CONTAINER_NAME_PREFIX}_#{DiscourseTheme::VERSION}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(settings:, dir:, args:, reset: false)
|
18
|
+
settings.local_discourse_directory = nil if reset
|
19
|
+
|
20
|
+
spec_path = "/spec"
|
21
|
+
index = dir.index(spec_path)
|
22
|
+
|
23
|
+
if index
|
24
|
+
spec_path = dir[index..-1]
|
25
|
+
dir = dir[0..index - 1]
|
26
|
+
end
|
27
|
+
|
28
|
+
spec_directory = File.join(dir, "/spec")
|
29
|
+
|
30
|
+
unless Dir.exist?(spec_directory)
|
31
|
+
raise DiscourseTheme::ThemeError.new "'#{spec_directory} does not exist"
|
32
|
+
end
|
33
|
+
|
34
|
+
configure_local_directory(settings)
|
35
|
+
|
36
|
+
headless = !!args.delete("--headless")
|
37
|
+
|
38
|
+
if settings.local_discourse_directory.empty?
|
39
|
+
run_tests_with_docker(
|
40
|
+
File.basename(dir),
|
41
|
+
spec_directory,
|
42
|
+
spec_path,
|
43
|
+
headless: headless,
|
44
|
+
verbose: !!args.delete("--verbose"),
|
45
|
+
rebuild: !!args.delete("--rebuild"),
|
46
|
+
)
|
47
|
+
else
|
48
|
+
run_tests_locally(
|
49
|
+
settings.local_discourse_directory,
|
50
|
+
spec_directory,
|
51
|
+
headless: headless,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def execute(command:, message: nil, exit_on_error: true, stream: false)
|
59
|
+
UI.progress(message) if message
|
60
|
+
|
61
|
+
success = false
|
62
|
+
output = +""
|
63
|
+
|
64
|
+
Open3.popen2e(command) do |stdin, stdout_and_stderr, wait_thr|
|
65
|
+
Thread.new do
|
66
|
+
stdout_and_stderr.each do |line|
|
67
|
+
puts line if stream
|
68
|
+
output << line
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
exit_status = wait_thr.value
|
73
|
+
success = exit_status.success?
|
74
|
+
|
75
|
+
unless success
|
76
|
+
UI.error "Error occured while running: `#{command}`:\n\n#{output}" unless stream
|
77
|
+
exit 1 if exit_on_error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
output
|
82
|
+
end
|
83
|
+
|
84
|
+
def run_tests_locally(local_directory, spec_path, headless: false)
|
85
|
+
UI.progress(
|
86
|
+
"Running RSpec tests using local Discourse repository located at '#{local_directory}'...",
|
87
|
+
)
|
88
|
+
|
89
|
+
Kernel.exec(
|
90
|
+
ENV,
|
91
|
+
"cd #{local_directory} && #{headless ? SELENIUM_HEADLESS_ENV : ""} bundle exec rspec #{spec_path}",
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_tests_with_docker(
|
96
|
+
theme_directory_name,
|
97
|
+
spec_directory,
|
98
|
+
spec_path,
|
99
|
+
headless: false,
|
100
|
+
verbose: false,
|
101
|
+
rebuild: false
|
102
|
+
)
|
103
|
+
image = "discourse/discourse_test:release"
|
104
|
+
UI.progress("Running RSpec tests using '#{image}' Docker image...")
|
105
|
+
|
106
|
+
unless Dir.exist?(DISCOURSE_THEME_TEST_TMP_DIR)
|
107
|
+
FileUtils.mkdir_p DISCOURSE_THEME_TEST_TMP_DIR
|
108
|
+
end
|
109
|
+
|
110
|
+
# Checks if the container is running
|
111
|
+
container_name = discourse_test_docker_container_name
|
112
|
+
is_running = false
|
113
|
+
|
114
|
+
if !(
|
115
|
+
output =
|
116
|
+
execute(
|
117
|
+
command: "docker ps -a --filter name=#{container_name} --format '{{json .}}'",
|
118
|
+
)
|
119
|
+
).empty?
|
120
|
+
is_running = JSON.parse(output)["State"] == "running"
|
121
|
+
end
|
122
|
+
|
123
|
+
if !is_running || rebuild
|
124
|
+
# Stop older versions of Docker container
|
125
|
+
existing_docker_container_ids =
|
126
|
+
execute(
|
127
|
+
command:
|
128
|
+
"docker ps -a -q --filter name=#{DISCOURSE_TEST_DOCKER_CONTAINER_NAME_PREFIX}",
|
129
|
+
).split("\n").join(" ")
|
130
|
+
|
131
|
+
if !existing_docker_container_ids.empty?
|
132
|
+
execute(command: "docker stop #{existing_docker_container_ids}")
|
133
|
+
execute(command: "docker rm -f #{existing_docker_container_ids}")
|
134
|
+
end
|
135
|
+
|
136
|
+
execute(
|
137
|
+
command: <<~CMD.squeeze(" "),
|
138
|
+
docker run -d \
|
139
|
+
-p 31337:31337 \
|
140
|
+
--add-host host.docker.internal:host-gateway \
|
141
|
+
--entrypoint=/sbin/boot \
|
142
|
+
--name=#{container_name} \
|
143
|
+
--pull=always \
|
144
|
+
-v #{DISCOURSE_THEME_TEST_TMP_DIR}:/tmp \
|
145
|
+
#{image}
|
146
|
+
CMD
|
147
|
+
message: "Creating #{image} Docker container...",
|
148
|
+
stream: verbose,
|
149
|
+
)
|
150
|
+
|
151
|
+
execute(
|
152
|
+
command:
|
153
|
+
"docker exec -u discourse:discourse #{container_name} ruby script/docker_test.rb --no-tests --checkout-ref origin/tests-passed",
|
154
|
+
message: "Checking out latest Discourse source code...",
|
155
|
+
stream: verbose,
|
156
|
+
)
|
157
|
+
|
158
|
+
execute(
|
159
|
+
command:
|
160
|
+
"docker exec -e SKIP_MULTISITE=1 -u discourse:discourse #{container_name} bundle exec rake docker:test:setup",
|
161
|
+
message: "Setting up Discourse test environment...",
|
162
|
+
stream: verbose,
|
163
|
+
)
|
164
|
+
|
165
|
+
execute(
|
166
|
+
command: "docker exec -u discourse:discourse #{container_name} bin/ember-cli --build",
|
167
|
+
message: "Building Ember CLI assets...",
|
168
|
+
stream: verbose,
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
rspec_envs = []
|
173
|
+
|
174
|
+
if headless
|
175
|
+
container_ip =
|
176
|
+
execute(
|
177
|
+
command:
|
178
|
+
"docker inspect #{container_name} --format '{{.NetworkSettings.IPAddress}}'",
|
179
|
+
).chomp("\n")
|
180
|
+
|
181
|
+
service =
|
182
|
+
start_chromedriver(allowed_origin: "host.docker.internal", allowed_ip: container_ip)
|
183
|
+
|
184
|
+
rspec_envs.push(SELENIUM_HEADLESS_ENV)
|
185
|
+
rspec_envs.push("CAPYBARA_SERVER_HOST=0.0.0.0")
|
186
|
+
rspec_envs.push(
|
187
|
+
"CAPYBARA_REMOTE_DRIVER_URL=http://host.docker.internal:#{service.uri.port}",
|
188
|
+
)
|
189
|
+
end
|
190
|
+
|
191
|
+
rspec_envs = rspec_envs.map { |env| "-e #{env}" }.join(" ")
|
192
|
+
|
193
|
+
begin
|
194
|
+
tmp_theme_directory = File.join(DISCOURSE_THEME_TEST_TMP_DIR, theme_directory_name)
|
195
|
+
FileUtils.mkdir_p(tmp_theme_directory) if !Dir.exist?(tmp_theme_directory)
|
196
|
+
FileUtils.cp_r(spec_directory, File.join(tmp_theme_directory))
|
197
|
+
|
198
|
+
execute(
|
199
|
+
command:
|
200
|
+
"docker exec #{rspec_envs} -t -u discourse:discourse #{container_name} bundle exec rspec #{File.join("/tmp", theme_directory_name, spec_path)}".squeeze(
|
201
|
+
" ",
|
202
|
+
),
|
203
|
+
stream: true,
|
204
|
+
)
|
205
|
+
ensure
|
206
|
+
FileUtils.rm_rf(File.join(tmp_theme_directory, "/spec"))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def configure_local_directory(settings)
|
211
|
+
return if settings.local_discourse_directory_configured?
|
212
|
+
|
213
|
+
should_configure_local_directory =
|
214
|
+
UI.yes?(
|
215
|
+
"Would you like to configure a local Discourse repository used to run the RSpec tests? If you select 'n', the tests will be run using a Docker container.",
|
216
|
+
)
|
217
|
+
|
218
|
+
if should_configure_local_directory
|
219
|
+
local_discourse_directory =
|
220
|
+
UI.ask("Please enter the path to the local Discourse directory:")
|
221
|
+
|
222
|
+
unless Dir.exist?(local_discourse_directory)
|
223
|
+
raise DiscourseTheme::ThemeError.new "'#{local_discourse_directory} does not exist"
|
224
|
+
end
|
225
|
+
|
226
|
+
unless File.exist?("#{local_discourse_directory}/lib/discourse.rb")
|
227
|
+
raise DiscourseTheme::ThemeError.new "'#{local_discourse_directory} is not a Discourse repository"
|
228
|
+
end
|
229
|
+
|
230
|
+
settings.local_discourse_directory = local_discourse_directory
|
231
|
+
else
|
232
|
+
settings.local_discourse_directory = ""
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def start_chromedriver(allowed_ip:, allowed_origin:)
|
237
|
+
service = Selenium::WebDriver::Service.chrome
|
238
|
+
options = Selenium::WebDriver::Options.chrome
|
239
|
+
service.executable_path = Selenium::WebDriver::DriverFinder.path(options, service.class)
|
240
|
+
service.args = ["--allowed-ips=#{allowed_ip}", "--allowed-origins=#{allowed_origin}"]
|
241
|
+
service.launch
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -44,7 +44,15 @@ module DiscourseTheme
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def get_themes_list
|
47
|
-
endpoint =
|
47
|
+
endpoint =
|
48
|
+
root +
|
49
|
+
(
|
50
|
+
if @is_theme_creator
|
51
|
+
"/user_themes.json"
|
52
|
+
else
|
53
|
+
"/admin/customize/themes.json"
|
54
|
+
end
|
55
|
+
)
|
48
56
|
|
49
57
|
response = request(Net::HTTP::Get.new(endpoint), never_404: true)
|
50
58
|
json = JSON.parse(response.body)
|
@@ -54,7 +62,13 @@ module DiscourseTheme
|
|
54
62
|
def get_raw_theme_export(id)
|
55
63
|
endpoint =
|
56
64
|
root +
|
57
|
-
(
|
65
|
+
(
|
66
|
+
if @is_theme_creator
|
67
|
+
"/user_themes/#{id}/export"
|
68
|
+
else
|
69
|
+
"/admin/customize/themes/#{id}/export"
|
70
|
+
end
|
71
|
+
)
|
58
72
|
|
59
73
|
response = request(Net::HTTP::Get.new endpoint)
|
60
74
|
raise "Error downloading theme: #{response.code}" unless response.code.to_i == 200
|
@@ -72,7 +86,14 @@ module DiscourseTheme
|
|
72
86
|
|
73
87
|
def upload_full_theme(tgz, theme_id:, components:)
|
74
88
|
endpoint =
|
75
|
-
root +
|
89
|
+
root +
|
90
|
+
(
|
91
|
+
if @is_theme_creator
|
92
|
+
"/user_themes/import.json"
|
93
|
+
else
|
94
|
+
"/admin/themes/import.json"
|
95
|
+
end
|
96
|
+
)
|
76
97
|
|
77
98
|
post =
|
78
99
|
Net::HTTP::Post::Multipart.new(
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
class DiscourseTheme::Config
|
3
|
-
|
4
3
|
class PathSetting
|
5
4
|
def initialize(config, path)
|
6
5
|
@config = config
|
@@ -39,6 +38,18 @@ class DiscourseTheme::Config
|
|
39
38
|
set("components", val)
|
40
39
|
end
|
41
40
|
|
41
|
+
def local_discourse_directory_configured?
|
42
|
+
!safe_config["local_discourse_directory"].nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def local_discourse_directory
|
46
|
+
safe_config["local_discourse_directory"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def local_discourse_directory=(dir)
|
50
|
+
set("local_discourse_directory", dir)
|
51
|
+
end
|
52
|
+
|
42
53
|
protected
|
43
54
|
|
44
55
|
def set(name, val)
|
@@ -79,7 +90,7 @@ class DiscourseTheme::Config
|
|
79
90
|
begin
|
80
91
|
@raw_config = YAML.load_file(@filename)
|
81
92
|
raise unless Hash === @raw_config
|
82
|
-
rescue
|
93
|
+
rescue StandardError
|
83
94
|
@raw_config = {}
|
84
95
|
$stderr.puts "ERROR: #{@filename} contains invalid config, resetting"
|
85
96
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require
|
2
|
+
require "zip"
|
3
3
|
|
4
4
|
class DiscourseTheme::Downloader
|
5
|
-
|
6
5
|
def initialize(dir:, client:)
|
7
6
|
@dir = dir
|
8
7
|
@client = client
|
@@ -26,7 +25,7 @@ class DiscourseTheme::Downloader
|
|
26
25
|
|
27
26
|
# Minitar extracts into a sub directory, move all the files up one dir
|
28
27
|
Dir.chdir(@dir) do
|
29
|
-
folders = Dir.glob(
|
28
|
+
folders = Dir.glob("*/")
|
30
29
|
raise "Extraction failed" unless folders.length == 1
|
31
30
|
FileUtils.mv(Dir.glob("#{folders[0]}*"), "./")
|
32
31
|
FileUtils.remove_dir(folders[0])
|
@@ -37,8 +36,6 @@ class DiscourseTheme::Downloader
|
|
37
36
|
private
|
38
37
|
|
39
38
|
def add_headers(request)
|
40
|
-
if @is_theme_creator
|
41
|
-
request["User-Api-Key"] = @api_key
|
42
|
-
end
|
39
|
+
request["User-Api-Key"] = @api_key if @is_theme_creator
|
43
40
|
end
|
44
41
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "date"
|
4
|
+
require "json"
|
5
5
|
|
6
6
|
module DiscourseTheme
|
7
7
|
class Scaffold
|
8
|
-
|
9
8
|
ABOUT_JSON = {
|
10
9
|
about_url: "TODO: Put your theme's public repo or Meta topic URL here",
|
11
10
|
license_url: "TODO: Put your theme's LICENSE URL here",
|
12
|
-
assets: {
|
11
|
+
assets: {
|
12
|
+
},
|
13
13
|
}
|
14
14
|
|
15
15
|
HELP = <<~STR
|
@@ -93,27 +93,29 @@ module DiscourseTheme
|
|
93
93
|
def self.generate(dir)
|
94
94
|
UI.progress "Generating a scaffold theme at #{dir}"
|
95
95
|
|
96
|
-
name =
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
96
|
+
name =
|
97
|
+
loop do
|
98
|
+
input = UI.ask("What would you like to call your theme?").to_s.strip
|
99
|
+
if input.empty?
|
100
|
+
UI.error("Theme name cannot be empty")
|
101
|
+
else
|
102
|
+
break input
|
103
|
+
end
|
102
104
|
end
|
103
|
-
end
|
104
105
|
|
105
106
|
is_component = UI.yes?("Is this a component?")
|
106
107
|
|
107
108
|
FileUtils.mkdir_p dir
|
108
109
|
Dir.chdir dir do
|
109
|
-
author =
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
110
|
+
author =
|
111
|
+
loop do
|
112
|
+
input = UI.ask("Who is authoring the theme?", default: ENV["USER"]).to_s.strip
|
113
|
+
if input.empty?
|
114
|
+
UI.error("Author cannot be empty")
|
115
|
+
else
|
116
|
+
break input
|
117
|
+
end
|
115
118
|
end
|
116
|
-
end
|
117
119
|
|
118
120
|
description = UI.ask("How would you describe this theme?").to_s.strip
|
119
121
|
|
@@ -125,19 +127,19 @@ module DiscourseTheme
|
|
125
127
|
about_template[:color_schemes] = {}
|
126
128
|
end
|
127
129
|
|
128
|
-
encoded_name = name.downcase.gsub(/[^a-zA-Z0-9_-]+/,
|
129
|
-
|
130
|
-
write(
|
131
|
-
write(
|
132
|
-
write(
|
133
|
-
write(
|
134
|
-
write(
|
135
|
-
write(
|
136
|
-
write(
|
137
|
-
write(
|
138
|
-
write(
|
130
|
+
encoded_name = name.downcase.gsub(/[^a-zA-Z0-9_-]+/, "_")
|
131
|
+
|
132
|
+
write("about.json", JSON.pretty_generate(about_template))
|
133
|
+
write("HELP", HELP)
|
134
|
+
write("LICENSE", LICENSE.sub("#YEAR", "#{Date.today.year}").sub("#AUTHOR", author))
|
135
|
+
write(".eslintrc", ESLINT_RC)
|
136
|
+
write(".gitignore", GIT_IGNORE)
|
137
|
+
write(".template-lintrc.js", TEMPLATE_LINT_RC)
|
138
|
+
write("package.json", PACKAGE_JSON.sub("#AUTHOR", author))
|
139
|
+
write("settings.yml", SETTINGS_YML)
|
140
|
+
write("common/common.scss", "")
|
139
141
|
write("javascripts/discourse/api-initializers/#{encoded_name}.js", API_INITIALIZER)
|
140
|
-
write(
|
142
|
+
write("locales/en.yml", EN_YML.sub("#DESCRIPTION", description))
|
141
143
|
|
142
144
|
UI.info "Initializing git repo"
|
143
145
|
puts `git init && git symbolic-ref HEAD refs/heads/main`
|
@@ -9,13 +9,13 @@ module DiscourseTheme
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def compress_dir(gzip, dir)
|
12
|
-
sgz = Zlib::GzipWriter.new(File.open(gzip,
|
12
|
+
sgz = Zlib::GzipWriter.new(File.open(gzip, "wb"))
|
13
13
|
tar = Archive::Tar::Minitar::Output.new(sgz)
|
14
14
|
|
15
15
|
Dir.chdir(dir + "/../") do
|
16
16
|
Find.find(File.basename(dir)) do |x|
|
17
17
|
bn = File.basename(x)
|
18
|
-
Find.prune if bn == "node_modules" || bn == "src" || bn[0] ==
|
18
|
+
Find.prune if bn == "node_modules" || bn == "src" || bn[0] == "."
|
19
19
|
next if File.directory?(x)
|
20
20
|
|
21
21
|
Minitar.pack_file(x, tar)
|
@@ -39,25 +39,18 @@ module DiscourseTheme
|
|
39
39
|
count
|
40
40
|
end
|
41
41
|
|
42
|
-
def upload_theme_field(target
|
42
|
+
def upload_theme_field(target:, name:, type_id:, value:)
|
43
43
|
raise "expecting theme_id to be set!" unless @theme_id
|
44
44
|
|
45
45
|
args = {
|
46
46
|
theme: {
|
47
|
-
theme_fields: [{
|
48
|
-
|
49
|
-
target: target,
|
50
|
-
type_id: type_id,
|
51
|
-
value: value
|
52
|
-
}]
|
53
|
-
}
|
47
|
+
theme_fields: [{ name: name, target: target, type_id: type_id, value: value }],
|
48
|
+
},
|
54
49
|
}
|
55
50
|
|
56
51
|
response = @client.update_theme(@theme_id, args)
|
57
52
|
json = JSON.parse(response.body)
|
58
|
-
|
59
|
-
UI.error "(end of errors)"
|
60
|
-
end
|
53
|
+
UI.error "(end of errors)" if diagnose_errors(json) != 0
|
61
54
|
end
|
62
55
|
|
63
56
|
def upload_full_theme
|
@@ -69,14 +62,11 @@ module DiscourseTheme
|
|
69
62
|
|
70
63
|
json = JSON.parse(response.body)
|
71
64
|
@theme_id = json["theme"]["id"]
|
72
|
-
if diagnose_errors(json) != 0
|
73
|
-
UI.error "(end of errors)"
|
74
|
-
end
|
65
|
+
UI.error "(end of errors)" if diagnose_errors(json) != 0
|
75
66
|
@theme_id
|
76
67
|
end
|
77
68
|
ensure
|
78
69
|
FileUtils.rm_f filename
|
79
70
|
end
|
80
|
-
|
81
71
|
end
|
82
72
|
end
|
@@ -15,38 +15,36 @@ module DiscourseTheme
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def watch
|
18
|
-
listener =
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
listener =
|
19
|
+
Listen.to(@dir) do |modified, added, removed|
|
20
|
+
begin
|
21
|
+
if modified.length == 1 && added.length == 0 && removed.length == 0 &&
|
22
|
+
(resolved = resolve_file(modified[0]))
|
23
|
+
target, name, type_id = resolved
|
24
|
+
UI.progress "Fast updating #{target}.scss"
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
value: File.read(modified[0]),
|
32
|
-
type_id: type_id
|
33
|
-
)
|
34
|
-
else
|
35
|
-
count = modified.length + added.length + removed.length
|
36
|
-
if count > 1
|
37
|
-
UI.progress "Detected changes in #{count} files, uploading theme"
|
26
|
+
@uploader.upload_theme_field(
|
27
|
+
target: target,
|
28
|
+
name: name,
|
29
|
+
value: File.read(modified[0]),
|
30
|
+
type_id: type_id,
|
31
|
+
)
|
38
32
|
else
|
39
|
-
|
40
|
-
|
33
|
+
count = modified.length + added.length + removed.length
|
34
|
+
if count > 1
|
35
|
+
UI.progress "Detected changes in #{count} files, uploading theme"
|
36
|
+
else
|
37
|
+
filename = modified[0] || added[0] || removed[0]
|
38
|
+
UI.progress "Detected changes in #{filename.gsub(@dir, "")}, uploading theme"
|
39
|
+
end
|
40
|
+
@uploader.upload_full_theme
|
41
41
|
end
|
42
|
-
|
42
|
+
UI.success "Done! Watching for changes..."
|
43
|
+
rescue DiscourseTheme::ThemeError => e
|
44
|
+
UI.error "#{e.message}"
|
45
|
+
UI.progress "Watching for changes..."
|
43
46
|
end
|
44
|
-
UI.success "Done! Watching for changes..."
|
45
|
-
rescue DiscourseTheme::ThemeError => e
|
46
|
-
UI.error "#{e.message}"
|
47
|
-
UI.progress "Watching for changes..."
|
48
47
|
end
|
49
|
-
end
|
50
48
|
|
51
49
|
listener.start
|
52
50
|
sleep unless self.class.return_immediately?
|
@@ -60,10 +58,10 @@ module DiscourseTheme
|
|
60
58
|
|
61
59
|
target, file = name.split("/")
|
62
60
|
|
63
|
-
if [
|
61
|
+
if %w[common desktop mobile].include?(target)
|
64
62
|
if file == "#{target}.scss"
|
65
63
|
# a CSS file
|
66
|
-
return
|
64
|
+
return target, "scss", 1
|
67
65
|
end
|
68
66
|
end
|
69
67
|
|
data/lib/discourse_theme.rb
CHANGED
@@ -1,29 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
2
|
+
require "fileutils"
|
3
|
+
require "pathname"
|
4
|
+
require "tempfile"
|
5
|
+
require "securerandom"
|
6
|
+
require "minitar"
|
7
|
+
require "zlib"
|
8
|
+
require "find"
|
9
|
+
require "net/http"
|
10
|
+
require "net/http/post/multipart"
|
11
|
+
require "uri"
|
12
|
+
require "listen"
|
13
|
+
require "json"
|
14
|
+
require "yaml"
|
15
|
+
require "tty/prompt"
|
16
16
|
|
17
|
-
require_relative
|
18
|
-
require_relative
|
19
|
-
require_relative
|
20
|
-
require_relative
|
21
|
-
require_relative
|
22
|
-
require_relative
|
23
|
-
require_relative
|
24
|
-
require_relative
|
25
|
-
require_relative
|
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"
|
26
26
|
|
27
27
|
module DiscourseTheme
|
28
|
-
class ThemeError < StandardError
|
28
|
+
class ThemeError < StandardError
|
29
|
+
end
|
29
30
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: discourse_theme
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitar
|
@@ -80,90 +80,104 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '1.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: selenium-webdriver
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.11'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.11'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: bundler
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
|
-
- - "
|
101
|
+
- - ">="
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
103
|
+
version: '0'
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
|
-
- - "
|
108
|
+
- - ">="
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
110
|
+
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: rake
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
|
-
- - "
|
115
|
+
- - ">="
|
102
116
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
117
|
+
version: '0'
|
104
118
|
type: :development
|
105
119
|
prerelease: false
|
106
120
|
version_requirements: !ruby/object:Gem::Requirement
|
107
121
|
requirements:
|
108
|
-
- - "
|
122
|
+
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
124
|
+
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: minitest
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
|
-
- - "
|
129
|
+
- - ">="
|
116
130
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
131
|
+
version: '0'
|
118
132
|
type: :development
|
119
133
|
prerelease: false
|
120
134
|
version_requirements: !ruby/object:Gem::Requirement
|
121
135
|
requirements:
|
122
|
-
- - "
|
136
|
+
- - ">="
|
123
137
|
- !ruby/object:Gem::Version
|
124
|
-
version: '
|
138
|
+
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: guard
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
|
-
- - "
|
143
|
+
- - ">="
|
130
144
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
145
|
+
version: '0'
|
132
146
|
type: :development
|
133
147
|
prerelease: false
|
134
148
|
version_requirements: !ruby/object:Gem::Requirement
|
135
149
|
requirements:
|
136
|
-
- - "
|
150
|
+
- - ">="
|
137
151
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
152
|
+
version: '0'
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: guard-minitest
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
142
156
|
requirements:
|
143
|
-
- - "
|
157
|
+
- - ">="
|
144
158
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
159
|
+
version: '0'
|
146
160
|
type: :development
|
147
161
|
prerelease: false
|
148
162
|
version_requirements: !ruby/object:Gem::Requirement
|
149
163
|
requirements:
|
150
|
-
- - "
|
164
|
+
- - ">="
|
151
165
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
166
|
+
version: '0'
|
153
167
|
- !ruby/object:Gem::Dependency
|
154
168
|
name: webmock
|
155
169
|
requirement: !ruby/object:Gem::Requirement
|
156
170
|
requirements:
|
157
|
-
- - "
|
171
|
+
- - ">="
|
158
172
|
- !ruby/object:Gem::Version
|
159
|
-
version: '
|
173
|
+
version: '0'
|
160
174
|
type: :development
|
161
175
|
prerelease: false
|
162
176
|
version_requirements: !ruby/object:Gem::Requirement
|
163
177
|
requirements:
|
164
|
-
- - "
|
178
|
+
- - ">="
|
165
179
|
- !ruby/object:Gem::Version
|
166
|
-
version: '
|
180
|
+
version: '0'
|
167
181
|
- !ruby/object:Gem::Dependency
|
168
182
|
name: rubocop
|
169
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +220,48 @@ dependencies:
|
|
206
220
|
- - ">="
|
207
221
|
- !ruby/object:Gem::Version
|
208
222
|
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: syntax_tree
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: syntax_tree-disable_ternary
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: mocha
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - ">="
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
258
|
+
type: :development
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - ">="
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '0'
|
209
265
|
description: CLI helper for creating Discourse themes
|
210
266
|
email:
|
211
267
|
- sam.saffron@gmail.com
|
@@ -217,6 +273,7 @@ files:
|
|
217
273
|
- ".github/workflows/ci.yml"
|
218
274
|
- ".gitignore"
|
219
275
|
- ".rubocop.yml"
|
276
|
+
- ".streerc"
|
220
277
|
- CHANGELOG.md
|
221
278
|
- CODE_OF_CONDUCT.md
|
222
279
|
- Gemfile
|
@@ -228,6 +285,7 @@ files:
|
|
228
285
|
- discourse_theme.gemspec
|
229
286
|
- lib/discourse_theme.rb
|
230
287
|
- lib/discourse_theme/cli.rb
|
288
|
+
- lib/discourse_theme/cli_commands/rspec.rb
|
231
289
|
- lib/discourse_theme/client.rb
|
232
290
|
- lib/discourse_theme/config.rb
|
233
291
|
- lib/discourse_theme/downloader.rb
|
@@ -248,7 +306,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
248
306
|
requirements:
|
249
307
|
- - ">="
|
250
308
|
- !ruby/object:Gem::Version
|
251
|
-
version:
|
309
|
+
version: 3.0.0
|
252
310
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
253
311
|
requirements:
|
254
312
|
- - ">="
|