mirror of
https://github.com/discourse/discourse.git
synced 2025-02-13 14:55:06 +00:00
The recorded runtime log was specified when running the plugin specs, but it failed to match the entries from the runtime log and the passed as command line arguments because the runtime log entries were not prefixed with `./`.
419 lines
13 KiB
Ruby
419 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
directory "plugins"
|
|
|
|
desc "install all official plugins (use GIT_WRITE=1 to pull with write access)"
|
|
task "plugin:install_all_official" do
|
|
skip = Set.new(%w[customer-flair poll])
|
|
|
|
STDERR.puts "Allowing write to all repos!" if ENV["GIT_WRITE"]
|
|
|
|
promises = []
|
|
failures = []
|
|
|
|
Plugin::Metadata::OFFICIAL_PLUGINS.each do |name|
|
|
next if skip.include? name
|
|
|
|
repo = "https://github.com/discourse/#{name}"
|
|
dir = repo.split("/").last
|
|
path = File.expand_path("plugins/" + dir)
|
|
|
|
if Dir.exist? path
|
|
STDOUT.puts "Skipping #{dir} cause it already exists!"
|
|
next
|
|
end
|
|
|
|
if ENV["GIT_WRITE"]
|
|
repo = repo.gsub("https://github.com/", "git@github.com:")
|
|
repo += ".git"
|
|
end
|
|
|
|
promises << Concurrent::Promise.execute do
|
|
attempts ||= 1
|
|
STDOUT.puts("Cloning '#{repo}' to '#{path}'...")
|
|
system("git clone --quiet #{repo} #{path}", exception: true)
|
|
rescue StandardError
|
|
if attempts == 3
|
|
failures << repo
|
|
abort
|
|
end
|
|
|
|
STDOUT.puts "Failed to clone #{repo}... trying again..."
|
|
attempts += 1
|
|
retry
|
|
end
|
|
end
|
|
|
|
Concurrent::Promise.zip(*promises).value!
|
|
failures.each { |repo| STDOUT.puts "Failed to clone #{repo}" } if failures.present?
|
|
end
|
|
|
|
desc "install plugin"
|
|
task "plugin:install", :repo do |t, args|
|
|
repo = ENV["REPO"] || ENV["repo"] || args[:repo]
|
|
name = ENV["NAME"] || ENV["name"] || File.basename(repo, ".git")
|
|
|
|
plugin_path = File.expand_path("plugins/" + name)
|
|
if File.directory?(File.expand_path(plugin_path))
|
|
abort("Plugin directory, " + plugin_path + ", already exists.")
|
|
end
|
|
|
|
clone_status = system("git clone " + repo + " " + plugin_path)
|
|
unless clone_status
|
|
FileUtils.rm_rf(plugin_path)
|
|
abort("Unable to clone plugin")
|
|
end
|
|
end
|
|
|
|
def update_plugin(plugin)
|
|
plugin = ENV["PLUGIN"] || ENV["plugin"] || plugin
|
|
plugin_path = plugin
|
|
plugin = File.basename(plugin)
|
|
|
|
unless File.directory?(plugin_path)
|
|
if File.directory?("plugins/" + plugin)
|
|
plugin_path = File.expand_path("plugins/" + plugin)
|
|
else
|
|
abort("Plugin " + plugin + " not found")
|
|
end
|
|
end
|
|
|
|
`git -C '#{plugin_path}' fetch origin --tags --force`
|
|
|
|
upstream_branch =
|
|
`git -C '#{plugin_path}' for-each-ref --format='%(upstream:short)' $(git -C '#{plugin_path}' symbolic-ref -q HEAD)`.strip
|
|
has_origin_main = `git -C '#{plugin_path}' branch -a`.match?(%r{remotes/origin/main\z})
|
|
has_local_main = `git -C '#{plugin_path}' show-ref refs/heads/main`.present?
|
|
|
|
if upstream_branch == "origin/master" && has_origin_main
|
|
puts "Branch has changed to `origin/main`"
|
|
|
|
if has_local_main
|
|
update_status = system("git -C '#{plugin_path}' checkout main")
|
|
abort("Unable to pull latest version of plugin #{plugin_path}") unless update_status
|
|
else
|
|
`git -C '#{plugin_path}' branch -m master main`
|
|
end
|
|
|
|
`git -C '#{plugin_path}' branch -u origin/main main`
|
|
end
|
|
|
|
update_status = system("git -C '#{plugin_path}' pull --quiet --no-rebase")
|
|
abort("Unable to pull latest version of plugin #{plugin_path}") unless update_status
|
|
end
|
|
|
|
desc "update all plugins"
|
|
task "plugin:update_all" do |t|
|
|
# Loop through each directory
|
|
plugins =
|
|
Dir
|
|
.glob(File.expand_path("plugins/*"))
|
|
.select { |f| File.directory?(f) && File.directory?("#{f}/.git") }
|
|
|
|
# run plugin:update
|
|
promises = plugins.map { |plugin| Concurrent::Promise.execute { update_plugin(plugin) } }
|
|
Concurrent::Promise.zip(*promises).value!
|
|
|
|
Rake::Task["plugin:versions"].invoke
|
|
end
|
|
|
|
desc "update a plugin"
|
|
task "plugin:update", :plugin do |t, args|
|
|
update_plugin(args[:plugin])
|
|
end
|
|
|
|
desc "pull compatible plugin versions for all plugins"
|
|
task "plugin:pull_compatible_all" do |t|
|
|
STDERR.puts <<~TEXT if GlobalSetting.load_plugins?
|
|
WARNING: Plugins were activated before running `rake plugin:pull_compatible_all`
|
|
You should prefix this command with LOAD_PLUGINS=0
|
|
TEXT
|
|
|
|
# Loop through each directory
|
|
plugins = Dir.glob(File.expand_path("plugins/*")).select { |f| File.directory? f }
|
|
# run plugin:pull_compatible
|
|
plugins.each do |plugin|
|
|
next unless File.directory?(plugin + "/.git")
|
|
Rake::Task["plugin:pull_compatible"].invoke(plugin)
|
|
Rake::Task["plugin:pull_compatible"].reenable
|
|
end
|
|
end
|
|
|
|
desc "pull a compatible plugin version"
|
|
task "plugin:pull_compatible", :plugin do |t, args|
|
|
plugin = ENV["PLUGIN"] || ENV["plugin"] || args[:plugin]
|
|
plugin_path = plugin
|
|
plugin = File.basename(plugin)
|
|
|
|
unless File.directory?(plugin_path)
|
|
if File.directory?("plugins/" + plugin)
|
|
plugin_path = File.expand_path("plugins/" + plugin)
|
|
else
|
|
abort("Plugin " + plugin + " not found")
|
|
end
|
|
end
|
|
|
|
checkout_version = Discourse.find_compatible_git_resource(plugin_path)
|
|
|
|
# Checkout value of the version compat
|
|
if checkout_version
|
|
puts "checking out compatible #{plugin} version: #{checkout_version}"
|
|
update_status =
|
|
system(
|
|
"git -C '#{plugin_path}' cat-file -e #{checkout_version} || git -C '#{plugin_path}' fetch --depth 1 $(git -C '#{plugin_path}' rev-parse --symbolic-full-name @{upstream} | awk -F '/' '{print $3}') #{checkout_version}; git -C '#{plugin_path}' reset --hard #{checkout_version}",
|
|
)
|
|
abort("Unable to checkout a compatible plugin version") unless update_status
|
|
else
|
|
puts "#{plugin} is already at latest compatible version"
|
|
end
|
|
end
|
|
|
|
desc "install all plugin gems"
|
|
task "plugin:install_all_gems" do |t|
|
|
# Left intentionally blank.
|
|
# When the app is being loaded, all missing gems are installed
|
|
# See: lib/plugin_gem.rb
|
|
puts "Done"
|
|
end
|
|
|
|
desc "install plugin gems"
|
|
task "plugin:install_gems", :plugin do |t, args|
|
|
# Left intentionally blank.
|
|
# When the app is being loaded, all missing gems are installed
|
|
# See: lib/plugin_gem.rb
|
|
puts "Done"
|
|
end
|
|
|
|
def spec(plugin, parallel: false, argv: nil)
|
|
params = []
|
|
params << "--profile" if !parallel
|
|
params << "--fail-fast" if ENV["RSPEC_FAILFAST"]
|
|
params << "--seed #{ENV["RSPEC_SEED"]}" if Integer(ENV["RSPEC_SEED"], exception: false)
|
|
params << argv if argv
|
|
|
|
# reject system specs as they are slow and need dedicated setup
|
|
files =
|
|
Dir.glob("plugins/#{plugin}/spec/**/*_spec.rb").reject { |f| f.include?("spec/system/") }.sort
|
|
|
|
if files.length > 0
|
|
cmd = parallel ? "bin/turbo_rspec" : "bin/rspec"
|
|
|
|
Rake::FileUtilsExt.verbose(!parallel) do
|
|
sh("LOAD_PLUGINS=1 #{cmd} #{files.join(" ")} #{params.join(" ")}") do |ok, status|
|
|
fail "Spec command failed with status (#{status.exitstatus})" if !ok
|
|
end
|
|
end
|
|
else
|
|
abort "No specs found."
|
|
end
|
|
end
|
|
|
|
desc "run plugin specs"
|
|
task "plugin:spec", %i[plugin argv] do |_, args|
|
|
args.with_defaults(plugin: "*")
|
|
spec(args[:plugin], argv: args[:argv])
|
|
end
|
|
|
|
desc "run plugin specs in parallel"
|
|
task "plugin:turbo_spec", %i[plugin argv] do |_, args|
|
|
args.with_defaults(plugin: "*")
|
|
spec(args[:plugin], parallel: true, argv: args[:argv])
|
|
end
|
|
|
|
desc "run plugin qunit tests"
|
|
task "plugin:qunit", %i[plugin timeout] do |t, args|
|
|
args.with_defaults(plugin: "*")
|
|
|
|
rake = "#{Rails.root}/bin/rake"
|
|
|
|
cmd = "LOAD_PLUGINS=1 "
|
|
|
|
target =
|
|
if args[:plugin] == "*"
|
|
puts "Running qunit tests for all plugins"
|
|
"plugins"
|
|
else
|
|
puts "Running qunit tests for #{args[:plugin]}"
|
|
args[:plugin]
|
|
end
|
|
|
|
cmd += "TARGET='#{target}' "
|
|
|
|
cmd += "#{rake} qunit:test"
|
|
cmd += "[#{args[:timeout]}]" if args[:timeout]
|
|
|
|
system cmd
|
|
exit $?.exitstatus
|
|
end
|
|
|
|
desc "run all migrations of a plugin"
|
|
namespace "plugin:migrate" do
|
|
def list_migrations(plugin_name)
|
|
plugin_root = File.join(Rails.root, "plugins", plugin_name)
|
|
migrations_root = File.join(plugin_root, "db", "{post_migrate,migrate}", "*.rb")
|
|
Dir[migrations_root]
|
|
.map { |migration_filename| File.basename(migration_filename)[/(^.*?)_/, 1].to_i }
|
|
.sort
|
|
end
|
|
|
|
def cmd(operation, migration_number)
|
|
"rails db:migrate:#{operation} LOAD_PLUGINS=1 VERSION=#{migration_number}"
|
|
end
|
|
|
|
task :down, [:plugin] do |t, args|
|
|
list_migrations(args[:plugin]).reverse.each do |migration_number|
|
|
sh cmd(:down, migration_number)
|
|
end
|
|
end
|
|
|
|
task :up, [:plugin] do |t, args|
|
|
list_migrations(args[:plugin]).each { |migration_number| sh cmd(:up, migration_number) }
|
|
end
|
|
end
|
|
|
|
desc "display all plugin versions"
|
|
task "plugin:versions" do |t, args|
|
|
versions =
|
|
Dir
|
|
.glob("*", base: "plugins")
|
|
.map { |plugin| [plugin, "plugins/#{plugin}", "plugins/#{plugin}/.git"] }
|
|
.select do |plugin, plugin_dir, plugin_git_dir|
|
|
File.directory?(plugin_dir) && File.exist?(plugin_git_dir)
|
|
end
|
|
.sort_by { |plugin, _, _| plugin }
|
|
.map do |plugin, _, plugin_git_dir|
|
|
version = `git --git-dir \"#{plugin_git_dir}\" rev-parse HEAD`
|
|
abort("unable to get #{plugin} version") unless version
|
|
[plugin, version.strip[0...8]]
|
|
end
|
|
.to_h
|
|
|
|
puts JSON.pretty_generate(versions)
|
|
end
|
|
|
|
desc "create a new plugin based on template"
|
|
task "plugin:create", [:name] do |t, args|
|
|
class StringHelpers
|
|
def self.to_snake_case(string)
|
|
return string if string.match?(/\A[a-z0-9_]+\z/)
|
|
string.dup.gsub!("-", "_")
|
|
end
|
|
|
|
def self.to_pascal_case(string)
|
|
return string if string.match?(/\A[A-Z][a-z0-9]+([A-Z][a-z0-9]+)*\z/)
|
|
string.dup.split("-").map(&:capitalize).join
|
|
end
|
|
|
|
def self.to_pascal_spaced_case(string)
|
|
return string if string.match?(/\A[A-Z][a-z0-9]+([A-Z][a-z0-9]+)*\z/)
|
|
string.dup.split("-").map(&:capitalize).join(" ")
|
|
end
|
|
|
|
def self.is_in_kebab_case?(string)
|
|
string.match?(/\A[a-z0-9]+(-[a-z0-9]+)*\z/)
|
|
end
|
|
end
|
|
|
|
plugin_name = args[:name]
|
|
|
|
abort("Supply a name for the plugin") if plugin_name.blank?
|
|
abort("Name must be in kebab-case") unless StringHelpers.is_in_kebab_case?(plugin_name)
|
|
|
|
plugin_path = File.expand_path("plugins/" + plugin_name)
|
|
|
|
abort("Plugin directory, " + plugin_path + ", already exists.") if File.directory?(plugin_path)
|
|
|
|
failures = []
|
|
repo = "https://github.com/discourse/discourse-plugin-skeleton"
|
|
begin
|
|
attempts ||= 1
|
|
STDOUT.puts("Cloning '#{repo}' to '#{plugin_path}'...")
|
|
system("git clone --quiet #{repo} #{plugin_path}", exception: true)
|
|
rescue StandardError
|
|
if attempts == 3
|
|
failures << repo
|
|
abort
|
|
end
|
|
|
|
STDOUT.puts "Failed to clone #{repo}... trying again..."
|
|
attempts += 1
|
|
retry
|
|
end
|
|
failures.each { |repo| STDOUT.puts "Failed to clone #{repo}" } if failures.present?
|
|
|
|
Dir.chdir(plugin_path) do # rubocop:disable Discourse/NoChdir
|
|
puts "Initializing git repository..."
|
|
|
|
FileUtils.rm_rf("#{plugin_path}/.git")
|
|
FileUtils.rm_rf(Dir.glob("#{plugin_path}/**/.gitkeep"))
|
|
system "git", "init", exception: true
|
|
system "git", "symbolic-ref", "HEAD", "refs/heads/main", exception: true
|
|
root_files = Dir.glob("*").select { |f| File.file?(f) }
|
|
system "git", "add", *root_files, exception: true
|
|
system "git", "add", ".", exception: true
|
|
system "git", "commit", "-m", "Initial commit", "--quiet", exception: true
|
|
end
|
|
|
|
puts "🚂 Renaming directories..."
|
|
|
|
File.rename File.expand_path("plugins/#{plugin_name}/lib/my_plugin_module"),
|
|
File.expand_path(
|
|
"plugins/#{plugin_name}/lib/#{StringHelpers.to_snake_case(plugin_name)}",
|
|
)
|
|
|
|
File.rename File.expand_path("plugins/#{plugin_name}/app/controllers/my_plugin_module"),
|
|
File.expand_path(
|
|
"plugins/#{plugin_name}/app/controllers/#{StringHelpers.to_snake_case(plugin_name)}",
|
|
)
|
|
|
|
to_update_files = # assume all start with ./#{plugin_name}/
|
|
[
|
|
"app/controllers/#{StringHelpers.to_snake_case(plugin_name)}/examples_controller.rb",
|
|
"config/locales/client.en.yml",
|
|
"config/routes.rb",
|
|
"config/settings.yml",
|
|
"lib/#{StringHelpers.to_snake_case(plugin_name)}/engine.rb",
|
|
"plugin.rb",
|
|
"README.md",
|
|
]
|
|
|
|
to_update_files.each do |file|
|
|
puts "🚂 Updating #{file}..."
|
|
|
|
updated_file = []
|
|
File.foreach("plugins/#{plugin_name}/#{file}") do |line|
|
|
updated_file << line
|
|
.gsub("MyPluginModule", StringHelpers.to_pascal_case(plugin_name))
|
|
.gsub("my_plugin_module", StringHelpers.to_snake_case(plugin_name))
|
|
.gsub("my-plugin", plugin_name)
|
|
.gsub("discourse-plugin-name", plugin_name)
|
|
.gsub("TODO_plugin_name", StringHelpers.to_snake_case(plugin_name))
|
|
.gsub("plugin_name_enabled", "#{StringHelpers.to_snake_case(plugin_name)}_enabled")
|
|
.gsub("discourse_plugin_name", StringHelpers.to_snake_case(plugin_name))
|
|
.gsub("Plugin Name", StringHelpers.to_pascal_spaced_case(plugin_name))
|
|
.gsub(
|
|
"lib/my_plugin_module/engine",
|
|
"lib/#{StringHelpers.to_snake_case(plugin_name)}/engine",
|
|
)
|
|
end
|
|
|
|
File.open("plugins/#{plugin_name}/#{file}", "w") { |f| f.write(updated_file.join("")) }
|
|
end
|
|
|
|
Dir.chdir(plugin_path) do # rubocop:disable Discourse/NoChdir
|
|
puts "Committing changes..."
|
|
system "git", "add", ".", exception: true
|
|
system "git",
|
|
"commit",
|
|
"-m",
|
|
"Update plugin skeleton with plugin name",
|
|
"--quiet",
|
|
exception: true
|
|
end
|
|
|
|
puts "Done! 🎉"
|
|
|
|
puts "Do not forget to update the README.md and plugin.rb file with the plugin description and the url of the plugin."
|
|
puts "You are ready to start developing your plugin! 🚀"
|
|
end
|