From 488f816be7313205b1f3b0ed6bef9f0d14ad90d3 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Thu, 4 Jun 2026 21:48:13 +0200 Subject: [PATCH 1/3] chore: migrate from release-please to release-drafter and manual bump version workflow --- .github/.release-please-manifest.json | 3 - .github/release-drafter.yml | 56 +++++++++++ .github/release-please-config.json | 19 ---- .github/workflows/bump-version.yml | 120 ++++++++++++++++++++++++ .github/workflows/ci.yml | 29 ------ .github/workflows/release-drafter.yml | 30 ++++++ .github/workflows/release.yml | 117 ++--------------------- .github/workflows/release_artifacts.yml | 107 --------------------- bin/bump-version | 45 +++++++++ 9 files changed, 260 insertions(+), 266 deletions(-) delete mode 100644 .github/.release-please-manifest.json create mode 100644 .github/release-drafter.yml delete mode 100644 .github/release-please-config.json create mode 100644 .github/workflows/bump-version.yml create mode 100644 .github/workflows/release-drafter.yml delete mode 100644 .github/workflows/release_artifacts.yml create mode 100755 bin/bump-version diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json deleted file mode 100644 index 96f1cd94..00000000 --- a/.github/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "1.3.0" -} diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..78d5c397 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,56 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +categories: + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - 'feat' + - title: '🐛 Bug Fixes' + labels: + - 'bug' + - 'fix' + - title: '🧰 Maintenance' + labels: + - 'chore' + - 'documentation' + - 'dependencies' + - 'refactor' + - 'style' + - 'test' + - 'ci' + +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' + +version-resolver: + major: + labels: + - 'major' + - 'breaking' + minor: + labels: + - 'minor' + - 'feature' + - 'feat' + - 'enhancement' + patch: + labels: + - 'patch' + - 'bug' + - 'fix' + - 'chore' + - 'refactor' + - 'style' + - 'test' + - 'ci' + default: patch + +template: | + $CHANGES + + --- + ### 💡 How to Release: + 1. Trigger the **Bump Version** workflow manually. Set the bump type to `auto` to use the version resolved above (`$RESOLVED_VERSION`), or set a specific override. This will create a version bump PR. + 2. Merge the version bump PR into `main`. + 3. Publish this draft release on GitHub. This will tag the commit and automatically build and publish the Docker images. diff --git a/.github/release-please-config.json b/.github/release-please-config.json deleted file mode 100644 index 9c2bca07..00000000 --- a/.github/release-please-config.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "packages": { - ".": { - "release-type": "ruby", - "package-name": "html2rss-web", - "include-component-in-tag": false, - "version-file": "config/version.rb", - "changelog-path": "CHANGELOG.md", - "extra-files": [ - { - "type": "json", - "path": "frontend/package.json", - "jsonpath": "$.version" - }, - "public/openapi.yaml" - ] - } - } -} diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 00000000..4fdce9b1 --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,120 @@ +name: Bump Version + +on: + workflow_dispatch: + inputs: + bump_type: + description: "Bump Type (auto uses Release Drafter resolved version)" + required: true + default: "auto" + type: choice + options: + - "auto" + - "patch" + - "minor" + - "major" + - "custom" + custom_version: + description: "Custom Version (only used if Bump Type is 'custom', e.g. 1.4.0)" + required: false + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + bump_version: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Resolve Version from Release Drafter + id: release_drafter + if: github.event.inputs.bump_type == 'auto' + uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter.yml + disable-releases: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Determine Target Version + id: determine_version + run: | + BUMP_TYPE="${{ github.event.inputs.bump_type }}" + CUSTOM_VERSION="${{ github.event.inputs.custom_version }}" + RESOLVED_VERSION="${{ steps.release_drafter.outputs.resolved_version }}" + + if [ "$BUMP_TYPE" = "auto" ]; then + if [ -z "$RESOLVED_VERSION" ]; then + echo "Error: Release Drafter did not return a resolved version." >&2 + exit 1 + fi + echo "target_version=$RESOLVED_VERSION" >> "$GITHUB_OUTPUT" + echo "Determined version: $RESOLVED_VERSION (via Release Drafter auto-resolve)" + elif [ "$BUMP_TYPE" = "custom" ]; then + if [ -z "$CUSTOM_VERSION" ]; then + echo "Error: Custom version must be provided if bump_type is 'custom'." >&2 + exit 1 + fi + echo "target_version=$CUSTOM_VERSION" >> "$GITHUB_OUTPUT" + echo "Determined version: $CUSTOM_VERSION (via manual input)" + else + echo "target_version=$BUMP_TYPE" >> "$GITHUB_OUTPUT" + echo "Determined version: $BUMP_TYPE (via manual bump type)" + fi + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + cache: true + cache_dependency_path: frontend/pnpm-lock.yaml + package_json_file: frontend/package.json + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: ".tool-versions" + + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + working-directory: frontend + + - name: Perform Version Bump + id: perform_bump + run: | + bundle exec ruby bin/bump-version "${{ steps.determine_version.outputs.target_version }}" + NEW_VERSION=$(ruby -I. -e "require 'config/version'; puts Html2rss::Web::VERSION") + echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT" + + - name: Regenerate OpenAPI spec and client + run: | + make openapi + make openapi-client + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.BUMP_VERSION_TOKEN || github.token }} + commit-message: "chore: bump version to v${{ steps.perform_bump.outputs.new_version }}" + branch: "bump-version/v${{ steps.perform_bump.outputs.new_version }}" + title: "chore: bump version to v${{ steps.perform_bump.outputs.new_version }}" + body: | + Automated version bump to `v${{ steps.perform_bump.outputs.new_version }}`. + + This PR contains the version update in `config/version.rb` and regenerated OpenAPI specs and frontend API client. + + ### 🚀 Next Steps + 1. Verify that all CI checks pass. + 2. Merge this Pull Request. + 3. Go to the draft release on GitHub and publish it. + assignees: ${{ github.actor }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3dea936..18b411d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,14 +58,8 @@ jobs: openapi: runs-on: ubuntu-latest - permissions: - contents: write steps: - uses: actions/checkout@v6 - with: - token: ${{ startsWith(github.head_ref, 'release-please--') && secrets.RELEASE_PLEASE_TOKEN || github.token }} - ref: ${{ startsWith(github.head_ref, 'release-please--') && github.head_ref || github.sha }} - fetch-depth: 0 - uses: ruby/setup-ruby@v1 with: @@ -88,30 +82,7 @@ jobs: working-directory: frontend - name: Verify generated OpenAPI spec and client are up to date - id: verify run: make openapi-verify - continue-on-error: ${{ startsWith(github.head_ref, 'release-please--') }} - - - name: Regenerate and commit OpenAPI artifacts on release PRs - if: steps.verify.outcome == 'failure' && startsWith(github.head_ref, 'release-please--') - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - make openapi - make openapi-client - - git add public/openapi.yaml frontend/src/api/generated/ - - if git diff --staged --quiet; then - echo "No changes to commit" - else - git commit -m "chore: regenerate openapi spec and client for version bump" - git push origin ${{ github.head_ref }} - - echo "## OpenAPI artifacts regenerated" >> "$GITHUB_STEP_SUMMARY" - echo "Version bump detected; OpenAPI spec and frontend client have been regenerated and committed." >> "$GITHUB_STEP_SUMMARY" - fi - name: Lint OpenAPI contract run: make openapi-lint diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..52bea7b1 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,30 @@ +name: Release Drafter + +on: + push: + branches: + - main + pull_request: + types: + - opened + - reopened + - synchronize + branches: + - main + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + contents: write + pull-requests: read + runs-on: ubuntu-latest + steps: + - name: Draft next release + uses: release-drafter/release-drafter@v6 + with: + config-name: release-drafter.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e102335a..fd972e09 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,125 +1,27 @@ name: release on: - # Release only after the CI workflow succeeds on main so Docker publishes - # are tied to a CI-validated commit instead of any direct branch push. - workflow_run: - workflows: - - ci - types: - - completed - branches: - - main - workflow_dispatch: + release: + types: [published] permissions: contents: read concurrency: - group: release-${{ github.event.workflow_run.head_sha || github.sha }} + group: release-${{ github.event.release.tag_name }} cancel-in-progress: true jobs: - guard: - runs-on: ubuntu-latest - outputs: - target_sha: ${{ steps.resolve.outputs.target_sha }} - target_ref: ${{ steps.resolve.outputs.target_ref }} - steps: - - name: Validate release trigger and resolve target - id: resolve - env: - EVENT_NAME: ${{ github.event_name }} - WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }} - WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - GITHUB_REF_VALUE: ${{ github.ref }} - GITHUB_SHA_VALUE: ${{ github.sha }} - run: | - if [ "$EVENT_NAME" = "workflow_run" ]; then - if [ "$WORKFLOW_CONCLUSION" != "success" ]; then - echo "Release requires successful CI on main; got conclusion=$WORKFLOW_CONCLUSION" >&2 - exit 1 - fi - - if [ -z "$WORKFLOW_HEAD_SHA" ] || [ -z "$WORKFLOW_HEAD_BRANCH" ]; then - echo "workflow_run payload missing head SHA or branch" >&2 - exit 1 - fi - - echo "target_sha=$WORKFLOW_HEAD_SHA" >> "$GITHUB_OUTPUT" - echo "target_ref=refs/heads/$WORKFLOW_HEAD_BRANCH" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - if [ "$GITHUB_REF_VALUE" != "refs/heads/main" ]; then - echo "Manual release is restricted to refs/heads/main; got $GITHUB_REF_VALUE" >&2 - exit 1 - fi - - echo "target_sha=$GITHUB_SHA_VALUE" >> "$GITHUB_OUTPUT" - echo "target_ref=$GITHUB_REF_VALUE" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "Unsupported event: $EVENT_NAME" >&2 - exit 1 - - release: - needs: - - guard - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - outputs: - release_created: ${{ steps.release.outputs.release_created }} - tag_name: ${{ steps.release.outputs.tag_name }} - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ needs.guard.outputs.target_sha }} - fetch-depth: 0 - - - name: Run release-please - id: release - uses: googleapis/release-please-action@v5 - with: - token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }} - config-file: .github/release-please-config.json - manifest-file: .github/.release-please-manifest.json - - - name: Summarize release outcome - env: - RELEASE_CREATED: ${{ steps.release.outputs.release_created }} - RELEASE_TAG: ${{ steps.release.outputs.tag_name }} - run: | - { - echo "## Release outcome" - echo - echo "- Release created: ${RELEASE_CREATED:-false}" - if [ -n "${RELEASE_TAG}" ]; then - echo "- Release tag: ${RELEASE_TAG}" - else - echo "- Release tag: none" - fi - } >> "$GITHUB_STEP_SUMMARY" - docker-publish: - if: needs.release.outputs.release_created == 'true' - needs: - - guard - - release runs-on: ubuntu-latest env: IMAGE_NAME: html2rss/web - TAG_SHA: ${{ needs.guard.outputs.target_sha }} - RELEASE_TAG: ${{ needs.release.outputs.tag_name }} + TAG_SHA: ${{ github.sha }} + RELEASE_TAG: ${{ github.event.release.tag_name }} steps: - uses: actions/checkout@v6 with: - ref: ${{ needs.guard.outputs.target_sha }} + ref: refs/tags/${{ github.event.release.tag_name }} fetch-depth: 0 - name: Set up QEMU @@ -136,9 +38,8 @@ jobs: - name: Compute Docker tags id: tags run: | - # Extract the version part (e.g., html2rss-web/v1.1.0 -> v1.1.0, or v1.1.0 -> v1.1.0) + # Extract the version part (e.g., v1.1.0 -> 1.1.0) clean_tag="${RELEASE_TAG##*/}" - # Remove leading 'v' if present (e.g., v1.1.0 -> 1.1.0) release_version="${clean_tag#v}" echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" @@ -168,7 +69,7 @@ jobs: tags: ${{ steps.tags.outputs.tags }} build-args: | BUILD_TAG=${{ env.RELEASE_VERSION }} - GIT_SHA=${{ needs.guard.outputs.target_sha }} + GIT_SHA=${{ env.TAG_SHA }} platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max @@ -178,7 +79,7 @@ jobs: org.opencontainers.image.created=${{ env.TIMESTAMP_ISO }} org.opencontainers.image.description=Generates RSS feeds of any website & serves to the web! org.opencontainers.image.ref.name=${{ env.RELEASE_TAG }} - org.opencontainers.image.revision=${{ needs.guard.outputs.target_sha }} + org.opencontainers.image.revision=${{ env.TAG_SHA }} org.opencontainers.image.source=https://github.com/${{ github.repository }} org.opencontainers.image.title=html2rss-web org.opencontainers.image.url=https://github.com/${{ github.repository }}/releases/tag/${{ env.RELEASE_TAG }} diff --git a/.github/workflows/release_artifacts.yml b/.github/workflows/release_artifacts.yml deleted file mode 100644 index cb4e9728..00000000 --- a/.github/workflows/release_artifacts.yml +++ /dev/null @@ -1,107 +0,0 @@ -name: refresh release artifacts - -on: - pull_request: - types: - - opened - - reopened - - synchronize - branches: - - main - push: - branches: - - release-please--branches--main - -permissions: - contents: read - -concurrency: - group: release-artifacts-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - verify-generated-artifacts: - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.event.pull_request.head.ref, 'release-please--branches--') - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Setup pnpm - uses: pnpm/action-setup@v6 - with: - cache: true - cache_dependency_path: frontend/pnpm-lock.yaml - package_json_file: frontend/package.json - - - uses: actions/setup-node@v6 - with: - node-version-file: ".tool-versions" - - - name: Install frontend dependencies - run: pnpm install --frozen-lockfile - working-directory: frontend - - - name: Refresh OpenAPI artifacts - run: | - make openapi - make openapi-client - - - name: Assert generated artifacts are current - run: git diff --exit-code -- public/openapi.yaml frontend/src/api/generated - - refresh-generated-artifacts: - if: github.event_name == 'push' && github.repository == 'html2rss/html2rss-web' && github.ref == 'refs/heads/release-please--branches--main' && github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ github.ref_name }} - fetch-depth: 0 - - - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Setup pnpm - uses: pnpm/action-setup@v6 - with: - cache: true - cache_dependency_path: frontend/pnpm-lock.yaml - package_json_file: frontend/package.json - - - uses: actions/setup-node@v6 - with: - node-version-file: ".tool-versions" - - - name: Install frontend dependencies - run: pnpm install --frozen-lockfile - working-directory: frontend - - - name: Refresh OpenAPI artifacts - run: | - make openapi - make openapi-client - - - name: Commit generated artifacts - run: | - if git diff --quiet -- public/openapi.yaml frontend/src/api/generated; then - echo "Generated artifacts already up to date" - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add public/openapi.yaml frontend/src/api/generated - git commit -m "chore: refresh release artifacts" - git push diff --git a/bin/bump-version b/bin/bump-version new file mode 100755 index 00000000..009ea3bb --- /dev/null +++ b/bin/bump-version @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative '../config/version' + +# Helper method to calculate the next semantic version. +# +# @param current [String] the current semantic version +# @param bump_type [String] the type of bump: major, minor, or patch +# @return [String] the calculated next version +def calculate_next_version(current, bump_type) + major, minor, patch = current.split('.').map(&:to_i) + case bump_type + when 'major' then "#{major + 1}.0.0" + when 'minor' then "#{major}.#{minor + 1}.0" + when 'patch' then "#{major}.#{minor}.#{patch + 1}" + else raise "Invalid bump type: #{bump_type}" + end +end + +target = ARGV[0] +abort "Usage: #{$PROGRAM_NAME} " if target.nil? || target.strip.empty? + +target = target.strip.downcase +current_version = Html2rss::Web::VERSION + +next_version = if %w[major minor patch].include?(target) + calculate_next_version(current_version, target) + else + target.delete_prefix('v') + end + +# Validate semantic version format +unless next_version.match?(/\A\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?\z/) + abort "Error: Target version '#{next_version}' is not a valid semantic version (X.Y.Z)." +end + +puts "Bumping version from #{current_version} to #{next_version}..." + +version_file_path = File.expand_path('../config/version.rb', __dir__) +version_content = File.read(version_file_path) +new_version_content = version_content.gsub(/VERSION = '.*'/, "VERSION = '#{next_version}'") +File.write(version_file_path, new_version_content) + +puts "Successfully updated config/version.rb to #{next_version}" From bcef3e90cb05a9293acaeb72432688b53d2b9b09 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Thu, 4 Jun 2026 21:55:32 +0200 Subject: [PATCH 2/3] chore: address PR review feedback on branch constraints and fork permissions --- .github/workflows/bump-version.yml | 7 +++++++ .github/workflows/release-drafter.yml | 1 + 2 files changed, 8 insertions(+) diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index 4fdce9b1..30a98c44 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -32,6 +32,13 @@ jobs: with: fetch-depth: 0 + - name: Assert main branch + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "Error: This workflow must be run against the main branch (refs/heads/main); got ${{ github.ref }}" >&2 + exit 1 + fi + - name: Resolve Version from Release Drafter id: release_drafter if: github.event.inputs.bump_type == 'auto' diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 52bea7b1..94a6fad1 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -17,6 +17,7 @@ permissions: jobs: update_release_draft: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository permissions: contents: write pull-requests: read From 96ccecb3ffd1c61c1269697d9edee9879b218498 Mon Sep 17 00:00:00 2001 From: Gil Desmarais Date: Thu, 4 Jun 2026 22:02:43 +0200 Subject: [PATCH 3/3] chore: dynamically resolve TAG_SHA from checked out ref inside release.yml --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fd972e09..0d2c5b9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,6 @@ jobs: runs-on: ubuntu-latest env: IMAGE_NAME: html2rss/web - TAG_SHA: ${{ github.sha }} RELEASE_TAG: ${{ github.event.release.tag_name }} steps: - uses: actions/checkout@v6 @@ -30,10 +29,11 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - - name: Get Git commit timestamp + - name: Get Git commit timestamp and SHA run: | echo "TIMESTAMP_EPOCH=$(git log -1 --format=%ct)" >> "$GITHUB_ENV" echo "TIMESTAMP_ISO=$(git log -1 --format=%cI)" >> "$GITHUB_ENV" + echo "TAG_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV" - name: Compute Docker tags id: tags