require_relative 'command_helper' class ReleaseProject include CommandHelper UPDATE_TYPES = %w[major minor patch custom].freeze TMP_FOLDER_NAME = "_RENUO_RELEASE_TEMP_#{rand(100_000_000)}".freeze MOVE_TO_TMP_FOLDER = "mkdir -p #{TMP_FOLDER_NAME} && cd #{TMP_FOLDER_NAME}".freeze def run(args) @project_name, @update_type, @version = args validate_args open_comparison_page checkout_project # in the meantime veto_comparison @version ||= calculate_version release ensure cleanup end private def validate_args validate_project_name validate_update_type validate_custom_version_format end def validate_project_name abort('>> No project name given.') unless @project_name end def validate_update_type unless valid_update_type? abort('>> Please provide the desired update type: major, minor, patch or custom. '\ 'If you are unsure about the type please read https://semver.org') end if !custom_version? && @version abort('>> Do not specify a version for a non-custom release. Given version will be ignored.') end abort('>> Please enter your desired version for the custom release.') if custom_version? && @version.nil? end def valid_update_type? UPDATE_TYPES.include? @update_type end def custom_version? @update_type == UPDATE_TYPES[3] end def validate_custom_version_format return if @version.nil? || @version =~ RenuoVersion::SEMVER_SCHEMA abort('>> Invalid Version Number. Use format X.Y.Z for your version.') end def veto_comparison abort unless agree('Are you fine with the changes you just reviewed in your browser?') end def open_comparison_page puts 'Opening browser to double-check what is going to be deployed…' open_path "https://github.com/#{@project_name}/compare/#{main_branch}...develop" end def checkout_project abort('>> Project not found on Github.') unless system("#{MOVE_TO_TMP_FOLDER} && gh repo clone #{@project_name}") system(cmd_in_folder("git checkout #{main_branch} && git pull origin #{main_branch} &&" \ "git checkout #{develop_branch} && git pull origin #{develop_branch}")) end def calculate_version RenuoVersion.create(current_version).bump(@update_type).to_s end def release ask_for_final_confirmation bump_version if finish_release_branch puts ">> Project successfully released with version #{@version}." else abort(">> Unable to finish release and push to #{main_branch}. Cancelling release.") end end def current_version @current_version ||= if `#{cmd_in_folder('git tag')}` == '' '0.0.0' else sorted_tags = `#{cmd_in_folder('git tag --sort=taggerdate')}`.split("\n").reverse sorted_tags.find { |tag| tag =~ RenuoVersion::SEMVER_SCHEMA }.strip end end def bump_version version_bumped = find_and_replace_version return unless version_bumped system(cmd_in_folder('git add . && git commit -m "bump version"')) end def find_and_replace_version find_files_with_version.split("\n").any? do |file_name| puts "Replace the version in #{file_name}?" print_version_found(file_name) if agree('confirm?') bump_version_in_file(file_name) true else false end end end def find_files_with_version excluded_dirs = %w[.git node_modules tmp].map { |dir| "--exclude-dir=#{dir}" }.join(' ') grep_current_version = "grep -rl -F #{excluded_dirs} --include='*.rb' #{current_version} ." `#{cmd_in_folder(grep_current_version)}` end def print_version_found(file_name) system(cmd_in_folder("grep -A 1 -B 1 --color #{current_version} #{file_name}")) end def bump_version_in_file(file_name) system(cmd_in_folder("sed -i '' 's|#{current_version}|#{@version}|g' #{file_name}")) end def finish_release_branch merge_branches push_branches_and_tags end def merge_branches system( cmd_in_folder( [ "GIT_MERGE_AUTOEDIT=no git checkout #{main_branch}", "git merge #{develop_branch} --no-edit", "git tag -a #{@version} -m \"Release with versioin: #{@version}\"" ].join(' && ') ) ) end def push_branches_and_tags push_branch(develop_branch) push_branch(main_branch) system(cmd_in_folder("git checkout #{main_branch} && git push origin #{main_branch} --tags")) end def push_branch(branch) system(cmd_in_folder("git checkout #{branch} && git push origin #{branch}")) end def ask_for_final_confirmation unless agree(">> Are you sure you wish to deploy '#{@project_name}' " \ "as a #{@update_type} release (#{current_version} => #{@version})?") abort('>> Cancelling Release.') end return unless Time.now.friday? && Time.now.hour >= 16 unless agree('>> Are you sure you want to deploy on late Friday afternoon? ' \ 'Did you think about your family...waiting for you at home?') abort('>> Very good. Go home now.') end end def cmd_in_folder(command) "#{move_and_cd} && #{command}" end def move_and_cd "#{MOVE_TO_TMP_FOLDER} && cd #{folder_name}" end def folder_name @project_name.split('/').last end def cleanup system("rm -rf #{TMP_FOLDER_NAME}") end def main_branch remote_repo = "git@github.com:#{@project_name}.git" @main_branch ||= `git ls-remote --heads #{remote_repo} main`.empty? ? 'master' : 'main' end def develop_branch 'develop' end end