From 465c25116b2377f0e6f31b1ff48cc5aa725a8699 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:32:14 +0000 Subject: [PATCH 1/3] chore(deps): Bump actions/setup-node from 6.4.0 to 7.0.0 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.4.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6.4.0...v7.0.0) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/git-commit-lint.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/git-commit-lint.yaml b/.github/workflows/git-commit-lint.yaml index 012a8ee..ab97685 100644 --- a/.github/workflows/git-commit-lint.yaml +++ b/.github/workflows/git-commit-lint.yaml @@ -45,7 +45,7 @@ jobs: - name: Set Up Node.js id: set_up_nodejs - uses: actions/setup-node@v6.4.0 + uses: actions/setup-node@v7.0.0 with: node-version: "lts/*" From 623b0e29d812419270b47e5a65fb59d2ba7880e0 Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Fri, 11 Sep 2026 13:57:11 -0300 Subject: [PATCH 2/3] feat: Request reviews from the authors of released changes - A release or publication pull request gathers the work of several people, and nothing told any of them that their changes were about to reach production. - It is an input so that a VCS repository whose releases do not warrant it can turn it off, and it defaults to being on, because the common case is wanting the authors to know. - The authors are read from the GitHub API rather than from the Git history, because only GitHub can resolve the email address of a commit to a GitHub user. The commits are paginated, as a release easily exceeds the size of a single page. - A review is requested from one user at a time, because GitHub rejects the whole request when a single one of the users cannot be a reviewer, for instance because they have left the organization, and a release must not fail over that. - In 'Release and Deploy to Production' the step runs last, so that an unreachable GitHub API cannot cost the release and deployment checklist, which is worth more than the review requests. - The header comment of each workflow no longer lists the steps, which the step names already carry, and says instead what the pull request it opens is for, which none of them does. Co-Authored-By: Claude Opus 5 --- .../task-release-and-deploy-to-prod.yaml | 80 ++++++++++++++++--- .../workflows/task-release-and-publish.yaml | 77 +++++++++++++++--- 2 files changed, 136 insertions(+), 21 deletions(-) diff --git a/.github/workflows/task-release-and-deploy-to-prod.yaml b/.github/workflows/task-release-and-deploy-to-prod.yaml index f6d1a5c..6b167fa 100644 --- a/.github/workflows/task-release-and-deploy-to-prod.yaml +++ b/.github/workflows/task-release-and-deploy-to-prod.yaml @@ -1,8 +1,7 @@ # GitHub Actions Workflow for 'Release and Deploy to Production' Task # -# It creates a release VCS branch with the changes that are not yet in the production VCS reference, -# opens a draft GitHub pull request for it, and comments a release and deployment checklist on that -# pull request. When there is nothing to release, it ends quietly. +# It opens a GitHub pull request to create a release and deploy it to production. It ends quietly +# when there is nothing to release. name: "Task: Release and Deploy to Production" @@ -27,6 +26,13 @@ on: kind: release kind: deploy description: Labels of the GitHub Pull Request, One per Line + request_reviews_from_change_authors: + type: boolean + required: false + default: true + description: >- + Whether to request a review of the GitHub pull request from every GitHub user who + authored one of the commits included in it. pre_deployment_checklist_items: type: string @@ -120,6 +126,7 @@ jobs: echo "changelog_file_path=${changelog_file_path:?}" >> "$GITHUB_OUTPUT" - name: Create GitHub Pull Request + id: create_github_pull_request if: ${{ steps.check_changes.outputs.has_changes == 'true' }} run: | label_args=() @@ -129,14 +136,18 @@ jobs: fi done <<< "$GITHUB_PULL_REQUEST_LABELS" - gh pr create \ - --base "$PRODUCTION_VCS_REF" \ - --head "$RELEASE_VCS_REF" \ - --draft \ - --title 'Release and deploy to production' \ - --body-file "$CHANGELOG_FILE_PATH" \ - --assignee "$RELEASE_ASSIGNEE" \ - "${label_args[@]}" + github_pull_request_url="$( + gh pr create \ + --base "$PRODUCTION_VCS_REF" \ + --head "$RELEASE_VCS_REF" \ + --draft \ + --title 'Release and deploy to production' \ + --body-file "$CHANGELOG_FILE_PATH" \ + --assignee "$RELEASE_ASSIGNEE" \ + "${label_args[@]}" + )" + + echo "url=${github_pull_request_url:?}" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ github.token }} RELEASE_ASSIGNEE: ${{ github.actor }} @@ -174,3 +185,50 @@ jobs: GH_TOKEN: ${{ github.token }} PRE_DEPLOYMENT_CHECKLIST_ITEMS: ${{ inputs.pre_deployment_checklist_items }} POST_DEPLOYMENT_CHECKLIST_ITEMS: ${{ inputs.post_deployment_checklist_items }} + + - name: Request Reviews from Change Authors + if: >- + ${{ steps.check_changes.outputs.has_changes == 'true' + && inputs.request_reviews_from_change_authors }} + run: | + github_pull_request_number="$( + gh pr view "$GITHUB_PULL_REQUEST_URL" --json number --jq '.number' + )" + + # The author of a commit is read from the GitHub API rather than from the Git history, + # because only GitHub can resolve the email address of a commit to a GitHub user. The + # commits are paginated, as a release easily exceeds the size of a single page. + echo "Getting the authors of the commits of pull request #$github_pull_request_number…" + commit_authors="$( + gh api --paginate \ + "repos/${GITHUB_REPOSITORY:?}/pulls/${github_pull_request_number:?}/commits" \ + --jq '.[].author.login // empty' \ + | sort -u + )" + + reviewers=() + while IFS= read -r commit_author; do + # A bot cannot be a reviewer, and neither can the pull request's own author, which is + # the bot of GitHub Actions because the pull request is created with its token. + case "$commit_author" in + '' | *'[bot]') continue ;; + esac + reviewers+=("$commit_author") + done <<< "$commit_authors" + + if [ "${#reviewers[@]}" -eq 0 ]; then + echo 'There is nobody to request a review from.' + exit 0 + fi + + # A review is requested from one user at a time, because GitHub rejects the whole request + # when a single one of the users cannot be a reviewer, for instance because they no longer + # have access to the VCS repository. A review that cannot be requested is not fatal. + for reviewer in "${reviewers[@]}"; do + echo "Requesting a review from '$reviewer'…" + gh pr edit "$GITHUB_PULL_REQUEST_URL" --add-reviewer "$reviewer" \ + || echo "::warning::Could not request a review from '$reviewer'." + done + env: + GH_TOKEN: ${{ github.token }} + GITHUB_PULL_REQUEST_URL: ${{ steps.create_github_pull_request.outputs.url }} diff --git a/.github/workflows/task-release-and-publish.yaml b/.github/workflows/task-release-and-publish.yaml index b41fe57..5002f72 100644 --- a/.github/workflows/task-release-and-publish.yaml +++ b/.github/workflows/task-release-and-publish.yaml @@ -1,7 +1,7 @@ # GitHub Actions Workflow for 'Release and Publish' Task # -# Once a release pull request has been merged into the staging VCS reference, it creates a -# publication VCS branch and opens a draft GitHub pull request that publishes the release. +# It opens a GitHub pull request to publish a previously created release. It must be called from a +# merged release pull request, whose title, head VCS reference and assignee it derives its own from. name: "Task: Release and Publish" @@ -20,6 +20,13 @@ on: task kind: publish description: Labels of the GitHub Pull Request, One per Line + request_reviews_from_change_authors: + type: boolean + required: false + default: true + description: >- + Whether to request a review of the GitHub pull request from every GitHub user who + authored one of the commits included in it. permissions: {} @@ -72,6 +79,7 @@ jobs: echo "CREATE_RELEASE_GITHUB_PULL_REQUEST_DESCRIPTION=${create_release_github_pull_request_description:?}" >> "$GITHUB_ENV" - name: Create GitHub Pull Request for Publication + id: create_github_pull_request run: | label_args=() while IFS= read -r label; do @@ -80,14 +88,63 @@ jobs: fi done <<< "$GITHUB_PULL_REQUEST_LABELS" - gh pr create \ - --base "$RELEASE_VCS_REF" \ - --head "$CREATE_RELEASE_VCS_REF" \ - --draft \ - --title "$CREATE_RELEASE_GITHUB_PULL_REQUEST_TITLE" \ - --body "$CREATE_RELEASE_GITHUB_PULL_REQUEST_DESCRIPTION" \ - --assignee "$RELEASE_ASSIGNEE" \ - "${label_args[@]}" + github_pull_request_url="$( + gh pr create \ + --base "$RELEASE_VCS_REF" \ + --head "$CREATE_RELEASE_VCS_REF" \ + --draft \ + --title "$CREATE_RELEASE_GITHUB_PULL_REQUEST_TITLE" \ + --body "$CREATE_RELEASE_GITHUB_PULL_REQUEST_DESCRIPTION" \ + --assignee "$RELEASE_ASSIGNEE" \ + "${label_args[@]}" + )" + + echo "url=${github_pull_request_url:?}" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ github.token }} GITHUB_PULL_REQUEST_LABELS: ${{ inputs.github_pull_request_labels }} + + - name: Request Reviews from Change Authors + if: ${{ inputs.request_reviews_from_change_authors }} + run: | + github_pull_request_number="$( + gh pr view "$GITHUB_PULL_REQUEST_URL" --json number --jq '.number' + )" + + # The author of a commit is read from the GitHub API rather than from the Git history, + # because only GitHub can resolve the email address of a commit to a GitHub user. The + # commits are paginated, as a release easily exceeds the size of a single page. + echo "Getting the authors of the commits of pull request #$github_pull_request_number…" + commit_authors="$( + gh api --paginate \ + "repos/${GITHUB_REPOSITORY:?}/pulls/${github_pull_request_number:?}/commits" \ + --jq '.[].author.login // empty' \ + | sort -u + )" + + reviewers=() + while IFS= read -r commit_author; do + # A bot cannot be a reviewer, and neither can the pull request's own author, which is + # the bot of GitHub Actions because the pull request is created with its token. + case "$commit_author" in + '' | *'[bot]') continue ;; + esac + reviewers+=("$commit_author") + done <<< "$commit_authors" + + if [ "${#reviewers[@]}" -eq 0 ]; then + echo 'There is nobody to request a review from.' + exit 0 + fi + + # A review is requested from one user at a time, because GitHub rejects the whole request + # when a single one of the users cannot be a reviewer, for instance because they no longer + # have access to the VCS repository. A review that cannot be requested is not fatal. + for reviewer in "${reviewers[@]}"; do + echo "Requesting a review from '$reviewer'…" + gh pr edit "$GITHUB_PULL_REQUEST_URL" --add-reviewer "$reviewer" \ + || echo "::warning::Could not request a review from '$reviewer'." + done + env: + GH_TOKEN: ${{ github.token }} + GITHUB_PULL_REQUEST_URL: ${{ steps.create_github_pull_request.outputs.url }} From 498d1b83d03d5be94e3cbef7de472d907e246934 Mon Sep 17 00:00:00 2001 From: Jose Tomas Robles Hahn Date: Fri, 11 Sep 2026 15:52:31 -0300 Subject: [PATCH 3/3] feat: Add a job summary to the release task workflows - A release runs on a schedule in most of the callers, and its outcome was only visible by opening the log of the run. What somebody has to act on is the pull request it opens, so the summary carries its URL first, right under the heading. - A run that finds nothing to release writes a summary of its own, because that is the outcome of most scheduled runs, and an empty summary does not distinguish it from a run that failed early. - The number of changes is now an output of the step that counts them, because the summary states it and the step that opens the pull request is the one that writes it. - The review step records which users were requested and which of them GitHub rejected, because a rejection is otherwise only a warning annotation that nobody reads. Co-Authored-By: Claude Opus 5 --- .../task-release-and-deploy-to-prod.yaml | 48 ++++++++++++++++--- .../workflows/task-release-and-publish.yaml | 37 +++++++++++--- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/.github/workflows/task-release-and-deploy-to-prod.yaml b/.github/workflows/task-release-and-deploy-to-prod.yaml index 6b167fa..835fa7e 100644 --- a/.github/workflows/task-release-and-deploy-to-prod.yaml +++ b/.github/workflows/task-release-and-deploy-to-prod.yaml @@ -91,6 +91,7 @@ jobs: run: | echo "Counting commits of '$RELEASE_VCS_REF' that are not in '$PRODUCTION_VCS_REF'…" change_count="$(git rev-list --count "${PRODUCTION_VCS_REF}..${RELEASE_VCS_REF}" --)" + echo "change_count=${change_count:?}" >> "$GITHUB_OUTPUT" if [ "${change_count:?}" -gt 0 ]; then echo "There are $change_count changes to release." @@ -98,6 +99,15 @@ jobs: else echo 'There is nothing to release. The remaining steps will be skipped.' echo 'has_changes=false' >> "$GITHUB_OUTPUT" + + { + echo '# Release and Deploy to Production' + echo + echo 'Nothing to release.' + echo + echo "- **From:** \`$RELEASE_VCS_REF\`" + echo "- **Into:** \`$PRODUCTION_VCS_REF\`" + } >> "$GITHUB_STEP_SUMMARY" fi - name: Prepare Release @@ -148,8 +158,19 @@ jobs: )" echo "url=${github_pull_request_url:?}" >> "$GITHUB_OUTPUT" + + { + echo '# Release and Deploy to Production' + echo + echo "$github_pull_request_url" + echo + echo "- **Changes:** $CHANGE_COUNT" + echo "- **From:** \`$RELEASE_VCS_REF\`" + echo "- **Into:** \`$PRODUCTION_VCS_REF\`" + } >> "$GITHUB_STEP_SUMMARY" env: GH_TOKEN: ${{ github.token }} + CHANGE_COUNT: ${{ steps.check_changes.outputs.change_count }} RELEASE_ASSIGNEE: ${{ github.actor }} CHANGELOG_FILE_PATH: ${{ steps.generate_changelog.outputs.changelog_file_path }} GITHUB_PULL_REQUEST_LABELS: ${{ inputs.github_pull_request_labels }} @@ -216,19 +237,32 @@ jobs: reviewers+=("$commit_author") done <<< "$commit_authors" - if [ "${#reviewers[@]}" -eq 0 ]; then - echo 'There is nobody to request a review from.' - exit 0 - fi - # A review is requested from one user at a time, because GitHub rejects the whole request # when a single one of the users cannot be a reviewer, for instance because they no longer # have access to the VCS repository. A review that cannot be requested is not fatal. + requested_reviewers=() + rejected_reviewers=() for reviewer in "${reviewers[@]}"; do echo "Requesting a review from '$reviewer'…" - gh pr edit "$GITHUB_PULL_REQUEST_URL" --add-reviewer "$reviewer" \ - || echo "::warning::Could not request a review from '$reviewer'." + if gh pr edit "$GITHUB_PULL_REQUEST_URL" --add-reviewer "$reviewer"; then + requested_reviewers+=("@$reviewer") + else + echo "::warning::Could not request a review from '$reviewer'." + rejected_reviewers+=("@$reviewer") + fi done + + { + if [ "${#requested_reviewers[@]}" -gt 0 ]; then + echo "- **Reviewers:** ${requested_reviewers[*]}" + else + echo '- **Reviewers:** nobody' + fi + + if [ "${#rejected_reviewers[@]}" -gt 0 ]; then + echo "- **Reviewers GitHub rejected:** ${rejected_reviewers[*]}" + fi + } >> "$GITHUB_STEP_SUMMARY" env: GH_TOKEN: ${{ github.token }} GITHUB_PULL_REQUEST_URL: ${{ steps.create_github_pull_request.outputs.url }} diff --git a/.github/workflows/task-release-and-publish.yaml b/.github/workflows/task-release-and-publish.yaml index 5002f72..d000a8b 100644 --- a/.github/workflows/task-release-and-publish.yaml +++ b/.github/workflows/task-release-and-publish.yaml @@ -100,6 +100,16 @@ jobs: )" echo "url=${github_pull_request_url:?}" >> "$GITHUB_OUTPUT" + + { + echo '# Release and Publish' + echo + echo "$github_pull_request_url" + echo + echo "- **From:** \`$CREATE_RELEASE_VCS_REF\`" + echo "- **Into:** \`$RELEASE_VCS_REF\`" + echo "- **Release:** $PREPARE_RELEASE_GITHUB_PULL_REQUEST_HTML_URL" + } >> "$GITHUB_STEP_SUMMARY" env: GH_TOKEN: ${{ github.token }} GITHUB_PULL_REQUEST_LABELS: ${{ inputs.github_pull_request_labels }} @@ -132,19 +142,32 @@ jobs: reviewers+=("$commit_author") done <<< "$commit_authors" - if [ "${#reviewers[@]}" -eq 0 ]; then - echo 'There is nobody to request a review from.' - exit 0 - fi - # A review is requested from one user at a time, because GitHub rejects the whole request # when a single one of the users cannot be a reviewer, for instance because they no longer # have access to the VCS repository. A review that cannot be requested is not fatal. + requested_reviewers=() + rejected_reviewers=() for reviewer in "${reviewers[@]}"; do echo "Requesting a review from '$reviewer'…" - gh pr edit "$GITHUB_PULL_REQUEST_URL" --add-reviewer "$reviewer" \ - || echo "::warning::Could not request a review from '$reviewer'." + if gh pr edit "$GITHUB_PULL_REQUEST_URL" --add-reviewer "$reviewer"; then + requested_reviewers+=("@$reviewer") + else + echo "::warning::Could not request a review from '$reviewer'." + rejected_reviewers+=("@$reviewer") + fi done + + { + if [ "${#requested_reviewers[@]}" -gt 0 ]; then + echo "- **Reviewers:** ${requested_reviewers[*]}" + else + echo '- **Reviewers:** nobody' + fi + + if [ "${#rejected_reviewers[@]}" -gt 0 ]; then + echo "- **Reviewers GitHub rejected:** ${rejected_reviewers[*]}" + fi + } >> "$GITHUB_STEP_SUMMARY" env: GH_TOKEN: ${{ github.token }} GITHUB_PULL_REQUEST_URL: ${{ steps.create_github_pull_request.outputs.url }}