class ReleaseProject 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…' system_command = case RbConfig::CONFIG['host_os'] when /mswin|mingw|cygwin/ 'start' when /darwin/ 'open' when /linux|bsd/ 'xdg-open' end system "#{system_command} 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 && git pull origin develop && git flow init -d')) end def calculate_version RenuoVersion.create(current_version).bump(@update_type).to_s end def release ask_for_final_confirmation start_release_branch 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 return @current_version if @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\" && git push -u origin release/#{@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 start_release_branch system(cmd_in_folder("git flow release start #{@version}")) end def finish_release_branch system(cmd_in_folder("GIT_MERGE_AUTOEDIT=no git flow release finish -m #{@version} #{@version} && " \ 'git checkout develop && git push origin develop && ' \ "git checkout #{main_branch} && git push origin #{main_branch} --tags")) 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 @main_branch ||= system(cmd_in_folder('git checkout main && git show-ref refs/heads/main')) ? 'main' : 'master' end end