discourse/script/theme-watcher
Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

187 lines
4.3 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils'
require 'pathname'
require 'tempfile'
require 'securerandom'
require 'minitar'
require 'zlib'
require 'find'
require 'net/http'
require 'net/http/post/multipart'
require 'uri'
require 'listen'
require 'json'
# Work in progress theme watcher for Discourse
#
# Monitor a theme directory locally and automatically keep it in sync with Discourse
def usage
puts "Usage: theme-watcher DIR SITE"
exit 1
end
WATCHER_SETTINGS_FILE = File.expand_path("~/.discourse-theme-watcher")
$api_key = ENV['DISCOURSE_API_KEY']
$dir = ARGV[0]
$site = ARGV[1]
$theme_id = nil
if $site !~ /https?:\/\//i
$site = "http://#{$site}"
end
puts "Watching #{$dir} and uploading changes to #{$site}"
if !$api_key && File.exist?(WATCHER_SETTINGS_FILE)
$api_key = File.read(WATCHER_SETTINGS_FILE).strip
puts "Using previously stored api key in #{WATCHER_SETTINGS_FILE}"
end
if !$api_key
puts "No API key found in DISCOURSE_API_KEY env var enter your API key: "
$api_key = STDIN.gets.strip
puts "Would you like me to store this API key in #{WATCHER_SETTINGS_FILE}? (Yes|No)"
answer = STDIN.gets.strip
if answer =~ /y(es)?/i
File.write WATCHER_SETTINGS_FILE, $api_key
end
end
if !File.exist?("#{$dir}/about.json")
puts "No about.json file found in #{dir}!"
puts
usage
end
def compress_dir(gzip, dir)
sgz = Zlib::GzipWriter.new(File.open(gzip, 'wb'))
tar = Archive::Tar::Minitar::Output.new(sgz)
Dir.chdir(dir + "/../") do
Find.find(File.basename(dir)) do |x|
Find.prune if File.basename(x)[0] == ?.
next if File.directory?(x)
Minitar.pack_file(x, tar)
end
end
ensure
tar.close
sgz.close
end
def diagnose_errors(json)
count = 0
json["theme"]["theme_fields"].each do |row|
if (error = row["error"]) && error.length > 0
if count == 0
puts
end
count += 1
puts
puts "Error in #{row["target"]} #{row["name"]}: #{row["error"]}"
puts
end
end
count
end
def upload_theme_field(target: , name: , type_id: , value:)
args = {
theme: {
theme_fields: [{
name: name,
target: target,
type_id: type_id,
value: value
}]
}
}
uri = URI.parse($site + "/admin/themes/#{$theme_id}?api_key=#{$api_key}")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Put.new(uri.request_uri, 'Content-Type' => 'application/json')
request.body = args.to_json
http.start do |h|
response = h.request(request)
if response.code.to_i == 200
json = JSON.parse(response.body)
if diagnose_errors(json) == 0
puts "(done)"
end
else
puts "Error importing field status: #{response.code}"
end
end
end
def upload_full_theme(dir, site)
filename = "#{Pathname.new(Dir.tmpdir).realpath}/bundle_#{SecureRandom.hex}.tar.gz"
compress_dir(filename, dir)
# new full upload endpoint
uri = URI.parse(site + "/admin/themes/import.json?api_key=#{$api_key}")
http = Net::HTTP.new(uri.host, uri.port)
File.open(filename) do |tgz|
request = Net::HTTP::Post::Multipart.new(
uri.request_uri,
"bundle" => UploadIO.new(tgz, "application/tar+gzip", "bundle.tar.gz"),
)
response = http.request(request)
if response.code.to_i == 201
json = JSON.parse(response.body)
$theme_id = json["theme"]["id"]
if diagnose_errors(json) == 0
puts "(done)"
end
else
puts "Error importing theme status: #{response.code}"
end
end
ensure
FileUtils.rm_f filename
end
print "Uploading theme: "
upload_full_theme($dir, $site)
def resolve_file(path)
dir_len = File.expand_path($dir).length
name = File.expand_path(path)[dir_len + 1..-1]
target, file = name.split("/")
if ["common", "desktop", "mobile"].include?(target)
if file = "#{target}.scss"
# a CSS file
return [target, "scss", 1]
end
end
nil
end
listener = Listen.to($dir) do |modified, added, removed|
if modified.length == 1 &&
added.length == 0 &&
removed.length == 0 &&
(target, name, type_id = resolve_file(modified[0]))
print "Updating #{target} #{name}: "
upload_theme_field(target: target, name: name, value: File.read(modified[0]), type_id: type_id)
else
print "Full re-sync is required, re-uploading theme: "
upload_full_theme($dir, $site)
end
end
listener.start
sleep