require 'pdk' module PDK module Util module ChangelogGenerator # Taken from the version regex in https://forgeapi.puppet.com/schemas/module.json VERSION_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/.freeze GEM = 'github_changelog_generator'.freeze # Raises if the github_changelog_generator is not available def self.github_changelog_generator_available! check_command = PDK::CLI::Exec::InteractiveCommand.new(PDK::CLI::Exec.bundle_bin, 'info', 'github_changelog_generator') check_command.context = :module result = check_command.execute! return if result[:exit_code].zero? raise PDK::CLI::ExitWithError, format('Unable to generate the changelog as the %{gem} gem is not included in this module\'s Gemfile', gem: GEM) end # Runs the Changelog Generator gem (in the module's context) to automatically create a CHANGLELOG.MD file # # @returns [String] The content of the new Changelog def self.generate_changelog github_changelog_generator_available! changelog_command = PDK::CLI::Exec::InteractiveCommand.new(PDK::CLI::Exec.bundle_bin, 'exec', 'rake', 'changelog') changelog_command.context = :module result = changelog_command.execute! raise PDK::CLI::ExitWithError, format('Error generating changelog: %{stdout}', stdout: result[:stdout]) unless result[:exit_code].zero? output = changelog_content raise PDK::CLI::ExitWithError, format('The generated changelog contains uncategorized Pull Requests. Please label them and try again. See %{changelog_file} for more details', changelog_file: changelog_file) if output.include?('UNCATEGORIZED PRS; GO LABEL THEM') # rubocop:disable Layout/LineLength output end # Computes the next version, based on the content of a changelog # # @param current_version [String, Gem::Version] The current version of the module # @return [String] The new version. May be the same as the current version if there are no notable changes def self.compute_next_version(current_version) raise PDK::CLI::ExitWithError, format('Invalid version string %{version}', version: current_version) unless VERSION_REGEX.match?(current_version) version = Gem::Version.create(current_version).segments PDK.logger.info format('Determing the target version from \'%{file}\'', file: changelog_file) # Grab all lines that start with ## between from the latest changes # For example given the changelog below # ``` # # Change log # # All notable changes to this project will be documented in this file. # # ## [v4.0.0](https://github.com/puppetlabs/puppetlabs-inifile/tree/v4. # # [Full Changelog](https://github.com/puppetlabs/puppetlabs-inifile/com --+ # | # ### Changed | # | # - pdksync - FM-8499 - remove ubuntu14 support [\#363](https://github. | It's this piece of text we are interested in # | # ### Added | # | # - FM-8402 add debian 10 support [\#352](https://github.com/puppetlabs | # | # ## [v3.1.0](https://github.com/puppetlabs/puppetlabs-inifile/tree/v3. | # --+ # [Full Changelog](https://github.com/puppetlabs/puppetlabs-inifile/com # # ### Added # # - FM-8222 - Port Module inifile to Litmus [\#344](https://github.com/ # - \(FM-8154\) Add Windows Server 2019 support [\#340](https://github. # - \(FM-8041\) Add RedHat 8 support [\#339](https://github.com/puppetl # ```` data = '' in_changelog_entry = false changelog_content.each_line do |line| line.strip! if line.start_with?('[') # We're leaving the latest changes so we can break break if in_changelog_entry in_changelog_entry = true end data += line if in_changelog_entry && line.start_with?('##') end # Check for meta headers in first two header line matches case data when /^### Changed/ # Major Version bump version[0] += 1 version[1] = 0 version[2] = 0 when /^### Added/ # Minor Version bump version[1] += 1 version[2] = 0 when /^### Fixed/ # Patch Version bump version[2] += 1 end version.join('.') end # Returns the top most version from the CHANGELOG file def self.latest_version latest = nil changelog_content.each_line do |line| line.strip! if line.start_with?('## [') latest = line[line.index('[') + 1..line.index(']') - 1].delete('v') break # stops after the top version is extracted end end latest end def self.changelog_file # Default Changelog file is CHANGELOG.md, but also search for the .MD prefix as well. @changelog_file ||= ['CHANGELOG.md', 'CHANGELOG.MD'].map { |file| PDK::Util::Filesystem.expand_path(file) }.find { |path| PDK::Util::Filesystem.file?(path) } end def self.changelog_content return '' if changelog_file.nil? PDK::Util::Filesystem.read_file(changelog_file, open_args: 'rb:utf-8') end end end end