lib/decidim/gem_manager.rb
# frozen_string_literal: true
require "active_support/core_ext/module/delegation"
require "open3"
module Decidim
#
# Handles a decidim components.
#
# Allows to perform custom operations on all modules in a given folder, or on
# specific module folders.
#
# Potential operations are:
#
# * Running custom commands, via the `run` method, such as releasing it, or
# installing it locally.
#
# * Updating version files from the main `.decidim-version` file in the root
# of the repository.
#
class GemManager
PARTICIPATORY_SPACES = %w(
participatory_processes
assemblies
conferences
).freeze
COMPONENTS = %w(
accountability
budgets
debates
meetings
pages
proposals
surveys
sortitions
blogs
).freeze
def initialize(dir)
@dir = File.expand_path(dir)
end
def run(command, out: $stdout)
interpolated_in_folder(command) do |cmd|
self.class.run(cmd, out:)
end
end
def capture(command, env: {}, with_stderr: true)
interpolated_in_folder(command) do |cmd|
self.class.capture(cmd, env:, with_stderr:)
end
end
def replace_gem_version
Dir.chdir(@dir) do
replace_file(
"lib/#{name.tr("-", "/")}/version.rb",
/def self\.version(\s*)"[^"]*"/,
"def self.version\\1\"#{version}\""
)
end
end
def replace_package_version
Dir.chdir(@dir) do
replace_file(
"package.json",
/^ "version": "[^"]*"/,
" \"version\": \"#{semver_friendly_version(version)}\""
)
end
end
def short_name
name.gsub(/decidim-/, "")
end
class << self
def capture(cmd, env: {}, with_stderr: true)
return Open3.capture2e(env, cmd) if with_stderr
Open3.capture2(env, cmd)
end
def run(cmd, out: $stdout)
system(cmd, out:)
end
def test_participatory_space
new("decidim-#{PARTICIPATORY_SPACES.sample}").run("rake")
end
def test_component
new("decidim-#{COMPONENTS.sample}").run("rake")
end
def replace_versions
all_dirs do |dir|
new(dir).replace_gem_version
end
package_dirs do |dir|
new(dir).replace_package_version
end
replace_antora_version
end
def install_all(out: $stdout)
run_all(
"gem build %name && mv %name-%version.gem ..",
include_root: false,
out:
)
new(root).run(
"gem build %name && gem install *.gem",
out:
)
end
def uninstall_all(out: $stdout)
run_all(
"gem uninstall %name -v %version --executables --force",
out:
)
new(root).run(
"rm decidim-*.gem",
out:
)
end
def run_all(command, out: $stdout, include_root: true)
all_dirs(include_root:) do |dir|
status = run_at(dir, command, out:)
break if !status && fail_fast?
end
end
def run_packages(command, out: $stdout)
package_dirs do |dir|
status = run_at(dir, command, out:)
break if !status && fail_fast?
end
end
def run_at(dir, command, out: $stdout)
attempts = 0
until (status = new(dir).run(command, out:))
attempts += 1
break if attempts > Decidim::GemManager.retry_times
end
status
end
def version
@version ||= File.read(version_file).strip
end
def replace_file(name, regexp, replacement)
new_content = File.read(name).gsub(regexp, replacement)
File.write(name, new_content)
end
def all_dirs(include_root: true)
dirs = plugins
dirs << "./" if include_root
dirs.each { |dir| yield(dir) }
end
def package_dirs
dirs = Dir.glob("#{root}/packages/*")
dirs.each { |dir| yield(dir) }
end
def plugins
Dir.glob("#{root}/decidim-*/")
end
def semver_friendly_version(a_version)
a_version.gsub(/\.pre/, "-pre").gsub(/\.dev/, "-dev").gsub(/.rc(\d*)/, "-rc\\1")
end
def fail_fast?
ENV.fetch("FAIL_FAST", nil) != "false"
end
def retry_times
ENV.fetch("RETRY_TIMES", 10).to_i
end
def patch_component_gemfile_generator
content = %(# frozen_string_literal: true
<%# This file is automatically generated. Do not edit this file, it will be overwritten
If you want to modify it you can do it in the Decidim::GemManager.patch_component_gemfile_generator method %>
source "https://rubygems.org"
ruby RUBY_VERSION
gem "decidim", "~> #{version}"
gem "decidim-<%= component_name %>", path: "."
gem "puma", "#{fetch_gemfile_version("puma")}"
gem "bootsnap", "#{fetch_gemfile_version("bootsnap")}"
group :development, :test do
gem "byebug", "#{fetch_gemfile_version("byebug")}", platform: :mri
gem "decidim-dev", "~> #{version}"
end
group :development do
gem "faker", "#{Gem.loaded_specs["decidim-dev"].dependencies.select { |a| a.name == "faker" }.first.requirement}"
gem "letter_opener_web", "#{fetch_gemfile_version("letter_opener_web")}"
gem "listen", "#{fetch_gemfile_version("listen")}"
gem "spring", "#{fetch_gemfile_version("spring")}"
gem "spring-watcher-listen", "#{fetch_gemfile_version("spring-watcher-listen")}"
gem "web-console", "#{fetch_gemfile_version("web-console")}"
end
)
file_path = File.join(root, "decidim-generators/lib/decidim/generators/component_templates/Gemfile.erb")
File.write(file_path, content)
end
private
def fetch_gemfile_version(bundle_name)
Bundler.definition.dependencies.select { |a| a.name == bundle_name }.first.requirement.to_s
end
def root
File.expand_path(File.join("..", ".."), __dir__)
end
def version_file
File.join(root, ".decidim-version")
end
def replace_antora_version
antora_conf = File.join(root, "docs/antora.yml")
docs_version = version.match?(/\.dev$/) ? "develop" : "v#{version.match(/(\d*).(\d*)/)[0]}"
File.write(antora_conf, File.read(antora_conf).sub(/^version: .+/, "version: #{docs_version}"))
end
end
private
delegate :plugins, :replace_file, :semver_friendly_version, :version, to: :class
def interpolated_in_folder(command)
Dir.chdir(@dir) do
yield command.gsub("%version", version).gsub("%name", name)
end
end
def folder_name
File.basename(@dir)
end
def name
plugins.map { |name| File.expand_path(name) }.include?(@dir) ? folder_name : "decidim"
end
end
end