discourse/lib/tasks/plugin.rake
Martin Brennan 5f119c57e8
DEV: Suppress verbose command failure output in plugin:turbo_spec (#25952)
Before this change, if the "Plugins backend" task on GitHub CI
failed, we would get a huge amount of extra output at the end
just to show the command that rake ran which failed (the bin/turbo_rspec
command). This is useless and just makes it hard to see the failing
specs. If you need the full command, it's already output at the
top of the "Plugins backend" task in the GitHub CI.
2024-02-29 14:35:31 +10:00

293 lines
8.8 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