class ReleaseProject UPDATE_TYPES = %w[major minor patch custom].freeze TMP_FOLDER_NAME = ('_RENUO_RELEASE_TEMP_' + rand(100_000_000).to_s).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_project_name validate_update_type validate_custom_version_format compare_develop_master checkout_project @version ||= calculate_version release ensure cleanup end private 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? abort('>> Invalid Version Number. Use format X.Y.Z for your version.') unless @version =~ /\d+\.\d+\.\d+/ end def compare_develop_master puts 'Please double-check what is going to be deployed.' open_comparison_page abort unless agree('Are you fine with those changes?') end def open_comparison_page system "open https://github.com/#{@project_name}/compare/master...develop" end def checkout_project unless system("#{MOVE_TO_TMP_FOLDER} && hub clone #{@project_name} && " \ "cd #{folder_name} && git checkout master && git pull origin master &&" \ 'git checkout develop && git pull origin develop && git flow init -d') abort('>> Project not found on Github.') end end def calculate_version current_version_parts = current_version.split('.').map(&:to_i) type_index = UPDATE_TYPES.index(@update_type) current_version_parts[type_index + 1..-1] = [0] * (2 - type_index) current_version_parts[type_index] += 1 current_version_parts.join('.') 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 master. Cancelling release.') end end def current_version return @current_version if @current_version describe = cmd_in_folder('git describe --tags') tag = '`git rev-list --tags --max-count=1`' @current_version = if `#{cmd_in_folder('git tag')}` == '' '0.0.0' else `#{describe} #{tag}`.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 master && git push origin master --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 end