diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aecaec33e..539d5912d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,36 +29,6 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - - name: Determine release version - id: version - run: | - # Read current version from version.rb - CURRENT_VERSION=$(grep -oP "VERSION = '\K[^']+" lib/aspera/cli/version.rb) - echo "Current version in version.rb: $CURRENT_VERSION" - - # Determine release version - if [ -n "${{ inputs.version }}" ]; then - RELEASE_VERSION="${{ inputs.version }}" - else - # Strip .pre suffix if present - RELEASE_VERSION="${CURRENT_VERSION%.pre}" - fi - echo "Release version: $RELEASE_VERSION" - echo "release_version=$RELEASE_VERSION" >> $GITHUB_OUTPUT - echo "GEM_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV - - # Determine next development version - if [ -n "${{ inputs.next_version }}" ]; then - NEXT_VERSION="${{ inputs.next_version }}" - else - # Parse version components and increment minor version - IFS='.' read -r MAJOR MINOR PATCH <<< "$RELEASE_VERSION" - NEXT_MINOR=$((MINOR + 1)) - NEXT_VERSION="${MAJOR}.${NEXT_MINOR}.0" - fi - echo "Next development version: ${NEXT_VERSION}.pre" - echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT - - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -75,58 +45,16 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Update version.rb for release - run: | - ruby build/lib/release_helper.rb update-version "${{ steps.version.outputs.release_version }}" - cat lib/aspera/cli/version.rb - - - name: Update CHANGELOG.md for release - run: | - ruby build/lib/release_helper.rb update-changelog "${{ steps.version.outputs.release_version }}" - - - name: Extract release notes - id: changelog - run: | - # Extract changelog and save to file for GitHub release - ruby build/lib/release_helper.rb extract-changelog > release_notes.md - cat release_notes.md - - - name: Build documentation - run: | - mkdir -p pkg - bundle exec rake doc:build - env: - GEM_VERSION: ${{ steps.version.outputs.release_version }} - - - name: Commit release changes - run: | - git add -A - git commit -m "Release v${{ steps.version.outputs.release_version }}" - - - name: Create and push tag - run: | - git tag -a "v${{ steps.version.outputs.release_version }}" -m "Version ${{ steps.version.outputs.release_version }}" - git push origin "v${{ steps.version.outputs.release_version }}" - - - name: Create GitHub Release + - name: Create release env: GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - gh release create "v${{ steps.version.outputs.release_version }}" \ - --title "Aspera CLI v${{ steps.version.outputs.release_version }}" \ - --notes-file release_notes.md - - - name: Update version.rb for next development cycle - run: | - ruby build/lib/release_helper.rb update-version "${{ steps.version.outputs.next_version }}.pre" - cat lib/aspera/cli/version.rb - - - name: Add new CHANGELOG section for next development cycle - run: | - ruby build/lib/release_helper.rb add-changelog-section "${{ steps.version.outputs.next_version }}" - - - name: Commit next development version - run: | - git add -A - git commit -m "Prepare for next development cycle (${{ steps.version.outputs.next_version }}.pre)" - git push origin main + ARGS="" + if [ -n "${{ inputs.version }}" ]; then + ARGS="[${{ inputs.version }}" + if [ -n "${{ inputs.next_version }}" ]; then + ARGS="${ARGS},${{ inputs.next_version }}" + fi + ARGS="${ARGS}]" + fi + bundle exec rake "release:create${ARGS}" diff --git a/.gitignore b/.gitignore index 0ca98af9c..c64819d83 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ .idea .vscode .bundle +# temporary file generated during release workflow +/release_notes.md diff --git a/build/lib/release_helper.rb b/build/lib/release_helper.rb deleted file mode 100644 index 2829b2375..000000000 --- a/build/lib/release_helper.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -require 'date' -require 'pathname' - -# Helper for release automation -module ReleaseHelper - TOP = Pathname.new(__dir__).parent.parent - CHANGELOG_FILE = TOP / 'CHANGELOG.md' - VERSION_FILE = TOP / 'lib/aspera/cli/version.rb' - - class << self - # Extract the latest changelog section (everything between first ## and second ##) - # Strips the version heading and release date lines - # @return [String] The changelog content for the latest version - def extract_latest_changelog - content = CHANGELOG_FILE.read - # Match from first ## heading to the next ## heading (or end of file) - match = content.match(/^(## .+?)(?=^## |\z)/m) - return '' unless match - - section = match[1].strip - # Remove the version heading (## X.Y.Z) and Released: line - section.sub(/\A## .+\n+Released: .+\n*/, '').strip - end - - # Update CHANGELOG.md for release: - # - Replace version.pre with version - # - Replace date placeholder with today's date - # @param version [String] The release version (without .pre) - def update_changelog_for_release(version) - content = CHANGELOG_FILE.read - today = Date.today.strftime('%Y-%m-%d') - - # Replace the .pre version heading with release version - content.sub!(/^## #{Regexp.escape(version)}\.pre$/, "## #{version}") - - # Replace the date placeholder - content.sub!(/^Released: \[Place date of release here\]$/, "Released: #{today}") - - CHANGELOG_FILE.write(content) - end - - # Add a new development section to CHANGELOG.md for the next version - # @param next_version [String] The next version (without .pre suffix) - def add_next_changelog_section(next_version) - content = CHANGELOG_FILE.read - - new_section = <<~SECTION - ## #{next_version}.pre - - Released: [Place date of release here] - - ### New Features - - ### Issues Fixed - - ### Breaking Changes - - SECTION - - # Insert after the header comment block (after the markdownlint line) - content.sub!( - /^(# Changes \(Release notes\)\n\n\n)\n/, - "\\1\n#{new_section}" - ) - - CHANGELOG_FILE.write(content) - end - - # Update version.rb with a new version - # @param version [String] The new version string - def update_version_file(version) - content = VERSION_FILE.read - content.sub!(/VERSION = '[^']+'/, "VERSION = '#{version}'") - VERSION_FILE.write(content) - end - end -end - -# CLI interface when run directly -if __FILE__ == $PROGRAM_NAME - command = ARGV.shift - case command - when 'extract-changelog' - puts ReleaseHelper.extract_latest_changelog - when 'update-changelog' - version = ARGV.shift || raise('Missing version argument') - ReleaseHelper.update_changelog_for_release(version) - puts "Updated CHANGELOG.md for release #{version}" - when 'add-changelog-section' - version = ARGV.shift || raise('Missing version argument') - ReleaseHelper.add_next_changelog_section(version) - puts "Added new section for #{version}.pre to CHANGELOG.md" - when 'update-version' - version = ARGV.shift || raise('Missing version argument') - ReleaseHelper.update_version_file(version) - puts "Updated version.rb to #{version}" - else - warn "Usage: #{$PROGRAM_NAME} [args]" - warn 'Commands:' - warn ' extract-changelog - Print latest changelog section' - warn ' update-changelog - Update changelog for release' - warn ' add-changelog-section - Add new .pre section' - warn ' update-version - Update version.rb' - exit 1 - end -end diff --git a/rakelib/release.rake b/rakelib/release.rake new file mode 100644 index 000000000..6cbc54f42 --- /dev/null +++ b/rakelib/release.rake @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require 'date' +require_relative '../build/lib/paths' +require_relative '../build/lib/build_tools' +include BuildTools + +# Release automation tasks +namespace :release do + CHANGELOG_FILE = Paths::TOP / 'CHANGELOG.md' + VERSION_FILE = Paths::TOP / 'lib/aspera/cli/version.rb' + RELEASE_NOTES_FILE = Paths::TOP / 'release_notes.md' + + # Read current version from version.rb + def current_version + VERSION_FILE.read[/VERSION = '([^']+)'/, 1] + end + + # Extract the latest changelog section (everything between first ## and second ##) + # Strips the version heading and release date lines + def extract_latest_changelog + content = CHANGELOG_FILE.read + # Match from first ## heading to the next ## heading (or end of file) + match = content.match(/^(## .+?)(?=^## |\z)/m) + return '' unless match + + section = match[1].strip + # Remove the version heading (## X.Y.Z) and Released: line + section.sub(/\A## .+\n+Released: .+\n*/, '').strip + end + + # Update CHANGELOG.md for release: + # - Replace version.pre with version + # - Replace date placeholder with today's date + def update_changelog_for_release(version) + content = CHANGELOG_FILE.read + today = Date.today.strftime('%Y-%m-%d') + + # Replace the .pre version heading with release version + content.sub!(/^## #{Regexp.escape(version)}\.pre$/, "## #{version}") + + # Replace the date placeholder + content.sub!(/^Released: \[Place date of release here\]$/, "Released: #{today}") + + CHANGELOG_FILE.write(content) + end + + # Add a new development section to CHANGELOG.md for the next version + def add_next_changelog_section(next_version) + content = CHANGELOG_FILE.read + + new_section = <<~SECTION + ## #{next_version}.pre + + Released: [Place date of release here] + + ### New Features + + ### Issues Fixed + + ### Breaking Changes + + SECTION + + # Insert after the header comment block (after the markdownlint line) + content.sub!( + /^(# Changes \(Release notes\)\n\n\n)\n/, + "\\1\n#{new_section}" + ) + + CHANGELOG_FILE.write(content) + end + + # Update version.rb with a new version + def update_version_file(version) + content = VERSION_FILE.read + content.sub!(/VERSION = '[^']+'/, "VERSION = '#{version}'") + VERSION_FILE.write(content) + end + + # Calculate next development version (increment minor version) + def calculate_next_version(release_version) + parts = release_version.split('.') + major, minor, _patch = parts[0].to_i, parts[1].to_i, parts[2].to_i + "#{major}.#{minor + 1}.0" + end + + desc 'Extract latest changelog section to release_notes.md' + task :extract_changelog do + notes = extract_latest_changelog + RELEASE_NOTES_FILE.write(notes) + puts "Extracted changelog to #{RELEASE_NOTES_FILE}" + puts notes + end + + desc 'Update CHANGELOG.md for release (remove .pre, set date)' + task :update_changelog, [:version] do |_t, args| + version = args[:version] || raise('Missing version argument') + update_changelog_for_release(version) + puts "Updated CHANGELOG.md for release #{version}" + end + + desc 'Add new .pre section to CHANGELOG.md' + task :add_changelog_section, [:version] do |_t, args| + version = args[:version] || raise('Missing version argument') + add_next_changelog_section(version) + puts "Added new section for #{version}.pre to CHANGELOG.md" + end + + desc 'Update version.rb' + task :update_version, [:version] do |_t, args| + version = args[:version] || raise('Missing version argument') + update_version_file(version) + puts "Updated version.rb to #{version}" + end + + desc 'Show current version' + task :version do + puts current_version + end + + desc 'Full release: update files, build docs, commit, tag, and create GitHub release' + task :create, [:version, :next_version] do |_t, args| + # Determine release version + release_version = args[:version] || current_version.sub(/\.pre$/, '') + next_version = args[:next_version] || calculate_next_version(release_version) + + puts "Release version: #{release_version}" + puts "Next development version: #{next_version}.pre" + + # Update version.rb for release + Rake::Task['release:update_version'].invoke(release_version) + + # Update CHANGELOG.md for release + Rake::Task['release:update_changelog'].invoke(release_version) + + # Extract release notes + Rake::Task['release:extract_changelog'].invoke + + # Build documentation + ENV['GEM_VERSION'] = release_version + Rake::Task['doc:build'].invoke + + # Build gem + Rake::Task['build'].invoke + + # Git operations + run('git', 'add', '-A') + run('git', 'commit', '-m', "Release v#{release_version}") + run('git', 'tag', '-a', "v#{release_version}", '-m', "Version #{release_version}") + run('git', 'push', 'origin', "v#{release_version}") + + # Create GitHub release with artifacts + pdf_file = Paths::RELEASE / "Manual-#{Aspera::Cli::Info::CMD_NAME}-#{release_version}.pdf" + gem_file = Paths::RELEASE / "#{Aspera::Cli::Info::GEM_NAME}-#{release_version}.gem" + + run('gh', 'release', 'create', "v#{release_version}", + '--title', "Aspera CLI v#{release_version}", + '--notes-file', RELEASE_NOTES_FILE.to_s, + pdf_file.to_s, + gem_file.to_s) + + # Prepare for next development cycle + Rake::Task['release:update_version'].reenable + Rake::Task['release:update_version'].invoke("#{next_version}.pre") + + Rake::Task['release:add_changelog_section'].invoke(next_version) + + run('git', 'add', '-A') + run('git', 'commit', '-m', "Prepare for next development cycle (#{next_version}.pre)") + run('git', 'push', 'origin', 'main') + + puts "\nRelease v#{release_version} complete!" + end +end diff --git a/release_notes.md b/release_notes.md deleted file mode 100644 index 305ad516b..000000000 --- a/release_notes.md +++ /dev/null @@ -1,8 +0,0 @@ -## 4.25.1 - -Released: 2026-01-21 - -### Issues Fixed - -* `build`: Fixed deploy workflow to use global rake instead of bundle exec. -* `build`: Made rspec require conditional in test.rake for deploy environment.