diff --git a/bin/build b/bin/build new file mode 100755 index 00000000..d8d4ad95 --- /dev/null +++ b/bin/build @@ -0,0 +1,58 @@ +#!/usr/bin/env ruby + +require "fileutils" +require "yaml" +require_relative "lib/build_config" +require_relative "lib/builder" +require_relative "lib/terminal" + +# Usage: +# +# $ ./build "all_variants" | "vanilla" | "default" | +# +# where is the name of a file in the `ci/configs` directory +# (minus the .yml extension) +# +# Examples: +# +# $ ./build all_variants # build every known variant +# $ ./build vanilla # build a vanilla Rails app (which doesn't use our template) +# $ ./build default # build using the default config +# $ ./build basic # build using the basic CI config +# $ ./build react # build using the React CI config +# +# You can capture the output to a file using the `tee` command: +# +# $ ./build all_variants | tee build.log +# +# If you want to capture the output to a file and keep the color output, you can +# use the `unbuffer` command: +# +# $ brew install expect # or your linux distro equivalent +# $ unbuffer ./build all_variants | tee build.log + +class Main + class << self + def main + configs = BuildConfig.resolve_to_configs(ARGV.first) + + Terminal.puts_header(configs_summary(configs)) + + configs.each do |config| + Builder.new(config:).build + end + end + + private + + def configs_summary(configs) + <<~EO_HEAD + Building apps for #{configs.length} configs: + - #{configs.map(&:name).join("\n - ")} + + EO_HEAD + end + end +end + +Main.main diff --git a/bin/create_comarison_repo_prs b/bin/create_comarison_repo_prs new file mode 100755 index 00000000..871e6da8 --- /dev/null +++ b/bin/create_comarison_repo_prs @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require_relative "lib/comparison_config" +require_relative "lib/terminal" + +class Main + class << self + def main(dry_run:) + Dir.chdir(ComparisonConfig.comparison_repo_root_path) do + ComparisonConfig.comparison_builds.each do |build_name| + Terminal.puts_header("Creating PR for #{build_name}") + cmd = "gh pr create --title 'Compare #{build_name} against vanilla Rails' --body 'Compare #{build_name} against vanilla Rails' --base main --head #{build_name} #{dry_run ? "--dry-run" : ""}" + system(cmd) + end + end + end + end +end + +Main.main(dry_run: ARGV.include?("--dry-run")) diff --git a/bin/create_comparison_repo b/bin/create_comparison_repo new file mode 100755 index 00000000..fb03b131 --- /dev/null +++ b/bin/create_comparison_repo @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +require "fileutils" +require_relative "lib/comparison_config" +require_relative "lib/terminal" + +class Main + class << self + def main + reset_comparison_repo + use_vanilla_rails_as_main_branch + + ComparisonConfig.comparison_builds.each do |build_name| + populate_new_branch_for_build(build_name:) + checkout_main + end + end + + private + + def reset_comparison_repo + repo_path = ComparisonConfig.comparison_repo_root_path + + FileUtils.rm_rf(repo_path) + FileUtils.mkdir_p(repo_path) + + Dir.chdir(repo_path) do + system("git init") + system("git remote add origin git@github.com:ackama/rails-template-variants-comparison.git") + end + end + + def use_vanilla_rails_as_main_branch + Terminal.puts_header("Adding vanilla Rails build to main branch") + copy_build(build_path: ComparisonConfig.vanilla_build_path) + commit_changes(commit_message: "Add #{ComparisonConfig.vanilla_build_name}") + end + + def populate_new_branch_for_build(build_name:) + Terminal.puts_header("Creating branch for #{build_name}") + + rm_rf_except_git_dir + create_new_branch(branch_name: build_name) + + build_path = File.join(ComparisonConfig.build_path, build_name) + copy_build(build_path:) + + commit_changes(commit_message: "Add #{build_name}") + end + + def checkout_main + Dir.chdir(ComparisonConfig.comparison_repo_root_path) do + system("git checkout main") + end + end + + # clean out all the files from the previous build (except .git) + def rm_rf_except_git_dir + Dir.chdir(ComparisonConfig.comparison_repo_root_path) do + keepers = [".git", "."] + deletable_files = Dir.glob("{*,.*}").reject { |f| keepers.include?(f) } + + FileUtils.rm_rf(deletable_files) + end + end + + def create_new_branch(branch_name:) + Dir.chdir(ComparisonConfig.comparison_repo_root_path) do + system("git checkout -b #{branch_name}") + end + end + + def commit_changes(commit_message:) + Dir.chdir(ComparisonConfig.comparison_repo_root_path) do + system("git add .") + # skip pre-commit hooks because they're not relevant here + system("git commit --quiet --no-verify -m '#{commit_message}'") + end + end + + def copy_build(build_path:) + system("rsync -qav --exclude='.git' --exclude='node_modules' --exclude='tmp' #{build_path}/ #{ComparisonConfig.comparison_repo_root_path}/") + end + end +end + +Main.main diff --git a/bin/lib/build_config.rb b/bin/lib/build_config.rb new file mode 100644 index 00000000..481e1313 --- /dev/null +++ b/bin/lib/build_config.rb @@ -0,0 +1,96 @@ +class BuildConfig + PROJECT_ROOT_PATH = File.absolute_path(File.join(__dir__, "../..")) + CI_CONFIGS_PATH = File.join(PROJECT_ROOT_PATH, "ci/configs") + BUILD_PATH = File.join(PROJECT_ROOT_PATH, "tmp/builds") + TEMPLATE_PATH = File.join(PROJECT_ROOT_PATH, "template.rb") + TARGET_VERSIONS_PATH = File.join(PROJECT_ROOT_PATH, "target_versions.yml") + DEFAULT_CONFIG_NAME = "readme_example".freeze + VANILLA_CONFIG_NAME = "vanilla".freeze + APP_NAME_SUFFIX = "template_app".freeze + CI_CONFIGS = Dir.children(CI_CONFIGS_PATH).map { |f| f.sub(".yml", "") } + AVAILABLE_CONFIG_NAMES = [ + VANILLA_CONFIG_NAME, + DEFAULT_CONFIG_NAME, + *CI_CONFIGS + ].freeze + TARGET_RAILS_MAJOR_MINOR = YAML.safe_load_file(TARGET_VERSIONS_PATH).fetch("target_rails_major_minor") + + def self.all_configs + AVAILABLE_CONFIG_NAMES.map { |name| new(name:) } + end + + ## + # Resolves the CLI parameter to a list of Config instances + # + def self.resolve_to_configs(cli_param) + return all_configs if cli_param == "all_variants" + return [new(name: cli_param)] if AVAILABLE_CONFIG_NAMES.include?(cli_param) + + [new(name: DEFAULT_CONFIG_NAME)] + end + + attr_reader :name, :app_name, :config_path + + def initialize(name:) + @name = name + @app_name = case name + when VANILLA_CONFIG_NAME + "#{name}_#{target_rails_major}_#{APP_NAME_SUFFIX}" + else + "#{name}_#{APP_NAME_SUFFIX}" + end + + @config_path = case name + when DEFAULT_CONFIG_NAME + File.join(PROJECT_ROOT_PATH, "ackama_rails_template.config.yml") + when VANILLA_CONFIG_NAME + nil + else + File.join(CI_CONFIGS_PATH, "#{name}.yml") + end + end + + def vanilla? + name == VANILLA_CONFIG_NAME + end + + def app_path + File.join(BUILD_PATH, app_name) + end + + def target_versions_path + TARGET_VERSIONS_PATH + end + + def build_path + BUILD_PATH + end + + def template_path + return nil if vanilla? + + TEMPLATE_PATH + end + + def inspect + <<~INSPECT + + INSPECT + end + + def target_rails_major_minor + TARGET_RAILS_MAJOR_MINOR + end + + def target_rails_major + TARGET_RAILS_MAJOR_MINOR.split(".").first + end +end diff --git a/bin/lib/builder.rb b/bin/lib/builder.rb new file mode 100644 index 00000000..a157837d --- /dev/null +++ b/bin/lib/builder.rb @@ -0,0 +1,102 @@ +require "rubygems" + +class Builder + def initialize(config:) + @config = config + @rails_cmd_version = build_rails_cmd_version(target_rails_major_minor: config.target_rails_major_minor) + end + + def build + Dir.chdir(@config.build_path) do + delete_any_previous_build + drop_dbs_from_previous_build + + if @config.vanilla? + Terminal.puts_header("Building vanilla Rails app") + system(build_vanilla_cmd_env, build_vanilla_cmd) + else + Terminal.puts_header("Building Rails app") + system(build_cmd_env, build_cmd) + end + end + end + + private + + def base_cmd_parts + [ + "rails _#{@rails_cmd_version}_ new #{@config.app_name}", + "-d postgresql", + "--skip-javascript", + "--skip-kamal", + "--skip-solid" + ] + end + + def build_rails_cmd_version(target_rails_major_minor:) + specs = Gem::Specification.find_all_by_name("rails") + + raise "No versions of gem '#{gem_name}' are installed" if specs.empty? + + version = specs + .map { _1.version.to_s } + .sort + .reverse + .find { |v| v.start_with?(target_rails_major_minor) } + test_cmd = "rails _#{version}_ -v" + expected_test_output = "Rails #{version}" + actual_test_output = `#{test_cmd}`.strip + + raise "Command failed: #{test_cmd}. Actual: #{actual_test_output}" unless expected_test_output == actual_test_output + + Terminal.puts_header("Using Rails version #{version}") + + version + end + + def delete_any_previous_build + FileUtils.rm_rf(@config.app_path) + end + + def build_cmd + cmd_parts = base_cmd_parts.append("-m #{@config.template_path}") + + puts <<~EO_INFO + Building command:: #{cmd_parts.inspect} + EO_INFO + + cmd_parts.join(" ") + end + + def build_cmd_env + cmd_env = { + "TARGET_VERSIONS_PATH" => @config.target_versions_path, + "CONFIG_PATH" => @config.config_path + } + + puts <<~EO_INFO + Building ENV: #{cmd_env.inspect} + EO_INFO + + cmd_env + end + + def build_vanilla_cmd_env + {} + end + + def build_vanilla_cmd + puts <<~EO_INFO + Building command: #{base_cmd_parts.inspect} + EO_INFO + + base_cmd_parts.join(" ") + end + + def drop_dbs_from_previous_build + Terminal.puts_header("Dropping databases from previous build") + system "psql -c 'DROP DATABASE IF EXISTS #{@config.app_name}_test;'" + system "psql -c 'DROP DATABASE IF EXISTS #{@config.app_name}_development;'" + system "psql -c 'DROP DATABASE IF EXISTS #{@config.app_name}_test;'" + end +end diff --git a/bin/lib/comparison_config.rb b/bin/lib/comparison_config.rb new file mode 100644 index 00000000..e27d6a3b --- /dev/null +++ b/bin/lib/comparison_config.rb @@ -0,0 +1,30 @@ +class ComparisonConfig + PROJECT_ROOT_PATH = File.absolute_path(File.join(__dir__, "../..")) + BUILD_PATH = File.join(PROJECT_ROOT_PATH, "tmp/builds") + + class << self + def build_path + BUILD_PATH + end + + def comparison_repo_root_path + File.join(PROJECT_ROOT_PATH, "tmp/rails-template-variants-comparison") + end + + def vanilla_build_name + all_builds.find { |build_name| build_name.start_with?("vanilla_") } + end + + def vanilla_build_path + File.join(BUILD_PATH, vanilla_build_name) + end + + def comparison_builds + all_builds - [vanilla_build_name] + end + + def all_builds + Dir.children(BUILD_PATH) + end + end +end diff --git a/bin/lib/terminal.rb b/bin/lib/terminal.rb new file mode 100644 index 00000000..f55fb5ae --- /dev/null +++ b/bin/lib/terminal.rb @@ -0,0 +1,12 @@ +class Terminal + class << self + def puts_header(title) + puts <<~EO_HEADER + + #{title} + #{"=" * title.length} + + EO_HEADER + end + end +end