You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by jd...@apache.org on 2025/09/11 19:20:48 UTC
(grails-core) branch 7.0.x updated (126e6fdc15 -> d2ce520daa)
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a change to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
from 126e6fdc15 Merge pull request #15066 from apache/stacktrace-additivity
new 508bfa1a27 chore: add scripts to help automate post-vote processes
new d2ce520daa chore: back to 0.0.1 now that grails publish is published
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.asf.yaml | 7 ++
.github/scripts/releaseDistributions.sh | 83 +++++++++++++++++++
.github/scripts/releaseJarFiles.sh | 141 ++++++++++++++++++++++++++++++++
.github/vote_templates/groovy_pmc.txt | 1 +
.github/vote_templates/staged.txt | 7 +-
.github/workflows/release.yml | 108 ++++++++++++++++++------
dependencies.gradle | 2 +-
7 files changed, 318 insertions(+), 31 deletions(-)
create mode 100755 .github/scripts/releaseDistributions.sh
create mode 100755 .github/scripts/releaseJarFiles.sh
(grails-core) 02/02: chore: back to 0.0.1 now that grails publish is published
Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit d2ce520daad5bea2a7151518c4f8c2ad5513062a
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Thu Sep 11 15:19:16 2025 -0400
chore: back to 0.0.1 now that grails publish is published
---
dependencies.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dependencies.gradle b/dependencies.gradle
index c3445cdc91..ac9123a640 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -30,7 +30,7 @@ ext {
'commons-text.version' : '1.13.1',
'directory-watcher.version' : '0.19.1',
'gradle-spock.version' : '2.3-groovy-3.0',
- 'grails-publish-plugin.version' : '0.0.1-SNAPSHOT',
+ 'grails-publish-plugin.version' : '0.0.1',
'grails-gdoc-engine.version' : '1.0.1',
'jansi.version' : '1.18',
'javaparser-core.version' : '3.27.0',
(grails-core) 01/02: chore: add scripts to help automate post-vote processes
Posted by jd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jdaugherty pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 508bfa1a270201137a0fb7f6afd7c736b3472fbf
Author: James Daugherty <jd...@jdresources.net>
AuthorDate: Wed Sep 10 13:55:26 2025 -0400
chore: add scripts to help automate post-vote processes
---
.asf.yaml | 7 ++
.github/scripts/releaseDistributions.sh | 83 +++++++++++++++++++
.github/scripts/releaseJarFiles.sh | 141 ++++++++++++++++++++++++++++++++
.github/vote_templates/groovy_pmc.txt | 1 +
.github/vote_templates/staged.txt | 7 +-
.github/workflows/release.yml | 108 ++++++++++++++++++------
6 files changed, 317 insertions(+), 30 deletions(-)
diff --git a/.asf.yaml b/.asf.yaml
index 92fe9f6c4a..60f332e399 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -18,6 +18,13 @@
github:
# deployment environments to delay releasing until the necessary steps have completed
environments:
+ close:
+ required_reviewers:
+ - id: grails-committers
+ type: Team
+ - id: jdaugherty
+ type: User
+ wait_timer: 0
docs:
required_reviewers:
- id: grails-committers
diff --git a/.github/scripts/releaseDistributions.sh b/.github/scripts/releaseDistributions.sh
new file mode 100755
index 0000000000..d935db6ea6
--- /dev/null
+++ b/.github/scripts/releaseDistributions.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# ./releaseDistributions.sh <tag> <username> <password>
+
+set -euo pipefail
+
+if [[ $# -ne 3 ]]; then
+ echo "Usage: $0 <tag> <username> <password>" >&2
+ exit 1
+fi
+
+RELEASE_TAG="$1"
+RELEASE_VERSION="${RELEASE_TAG#v}"
+SVN_USER="$2"
+SVN_PASS="$3"
+RELEASE_ROOT="https://dist.apache.org/repos/dist/release/incubator/grails/core"
+DEV_ROOT="https://dist.apache.org/repos/dist/dev/incubator/grails/core"
+
+if [[ -z "${RELEASE_TAG}" ]]; then
+ echo "â ERROR: Release Tag must not be empty." >&2
+ exit 1
+fi
+if [[ -z "${SVN_USER}" ]]; then
+ echo "â ERROR: Username must not be empty." >&2
+ exit 1
+fi
+if [[ -z "${SVN_PASS}" ]]; then
+ echo "â ERROR: Password must not be empty." >&2
+ exit 1
+fi
+
+svn_flags=(--non-interactive --trust-server-cert --username "${SVN_USER}" --password "${SVN_PASS}")
+
+svn_exists() {
+ local url="$1"
+ svn ls "${svn_flags[@]}" --depth=empty "${url}" >/dev/null 2>&1
+}
+
+old_release_folder="$(svn ls "${svn_flags[@]}" "${RELEASE_ROOT}" | awk -F/ 'NF{print $1; exit}')"
+if [[ -n "${old_release_folder}" ]]; then
+ PRIOR_RELEASE_URL="${RELEASE_ROOT}/${old_release_folder}"
+ echo "đī¸ Deleting old release folder: ${PRIOR_RELEASE_URL}"
+ svn rm "${svn_flags[@]}" -m "Remove previous release ${old_release_folder}" "${PRIOR_RELEASE_URL}"
+ echo "â
Deleted old release folder"
+else
+ echo "âšī¸ No existing release subfolder found under ${RELEASE_ROOT}"
+fi
+
+DEV_VERSION_URL="$DEV_ROOT/${RELEASE_VERSION}"
+RELEASE_VERSION_URL="$RELEASE_ROOT/${RELEASE_VERSION}"
+
+if ! svn_exists "${DEV_VERSION_URL}"; then
+ echo "â ERROR: dev folder for ${RELEASE_VERSION} does not exist at: ${DEV_VERSION_URL}" >&2
+ exit 2
+fi
+
+if svn_exists "${RELEASE_VERSION_URL}"; then
+ echo "â ERROR: release folder for ${RELEASE_VERSION} already exists at: ${RELEASE_VERSION_URL}" >&2
+ exit 3
+fi
+
+echo "đ Promoting ${DEV_VERSION_URL} -> ${RELEASE_VERSION_URL}"
+svn mv "${svn_flags[@]}" -m "Promote Apache Grails (incubating) ${RELEASE_VERSION} from dev to release" "${DEV_VERSION_URL}" "${RELEASE_VERSION_URL}"
+echo "â
Promoted"
diff --git a/.github/scripts/releaseJarFiles.sh b/.github/scripts/releaseJarFiles.sh
new file mode 100755
index 0000000000..8421782d74
--- /dev/null
+++ b/.github/scripts/releaseJarFiles.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# ./releaseJarFiles.sh <staging repo description> <username> <password>
+
+set -euo pipefail
+
+if [[ $# -ne 3 ]]; then
+ echo "Usage: $0 <staging repo description> <username> <password>" >&2
+ exit 1
+fi
+
+NEXUS_URL="https://repository.apache.org"
+STAGING_DESCRIPTION="$1"
+NEXUS_USER="$2"
+NEXUS_PASS="$3"
+
+if [[ -z "${STAGING_DESCRIPTION}" ]]; then
+ echo "ERROR: Staging Description must not be empty." >&2
+ exit 1
+fi
+if [[ -z "${NEXUS_USER}" ]]; then
+ echo "ERROR: Username must not be empty." >&2
+ exit 1
+fi
+if [[ -z "${NEXUS_PASS}" ]]; then
+ echo "ERROR: Password must not be empty." >&2
+ exit 1
+fi
+
+nexusApi() {
+ local request_method="$1"; shift
+ local path="$1"; shift
+ curl -fsS -u "${NEXUS_USER}:${NEXUS_PASS}" \
+ -H 'Accept: application/json' \
+ -H 'Content-Type: application/json' \
+ -X "${request_method}" "${NEXUS_URL}/service/local/${path}" "$@"
+}
+
+wait_for_promotion() {
+ local repoId="$1"
+ local timeout_s="${2:-600}" # default 10 minutes
+ local interval_s="${3:-3}"
+ local started
+ started="$(date +%s)"
+
+ echo "Waiting for release promotion to complete (timeout ${timeout_s}s)âĻ"
+
+ while :; do
+ # 1) If any ERROR appears in activity, fail fast
+ act="$(nexusApi GET "/staging/repository/${repoId}/activity" || true)"
+ if [[ -n "$act" ]]; then
+ err_count="$(jq -r '[.. | objects? | select(has("severity")) | select(.severity=="ERROR")] | length' <<<"$act" 2>/dev/null || echo 0)"
+ if [[ "$err_count" != "0" ]]; then
+ echo "ERROR: Staging activity contains failure(s). Aborting." >&2
+ # Optionally dump recent relevant lines:
+ jq -r '.. | objects? | select(has("severity")) | "\(.severity): \(.name // "event") - \(.message // "")"' <<<"$act" || true
+ return 1
+ fi
+ fi
+
+ # 2) Check transitioning flag â when false after promote, action is done
+ trans="$(nexusApi GET '/staging/profile_repositories' \
+ | jq -r --arg r "$repoId" '.data[]? | select(.repositoryId==$r) | .transitioning' 2>/dev/null || echo "true")"
+
+ if [[ "$trans" == "false" ]]; then
+ # sanity: make sure we actually saw some "release/promote" activity; otherwise keep waiting a bit
+ if [[ -n "$act" ]]; then
+ # did we see any promote/release-ish step?
+ saw_promote="$(jq -r '
+ [ .. | objects? | .name? // empty | ascii_downcase
+ | select(test("release|promote|finish")) ] | length' <<<"$act" 2>/dev/null || echo 0)"
+ if [[ "$saw_promote" -gt 0 ]]; then
+ echo "Promotion appears complete."
+ return 0
+ fi
+ fi
+ fi
+
+ # timeout?
+ now="$(date +%s)"
+ if (( now - started >= timeout_s )); then
+ echo "ERROR: Timed out waiting for promotion to complete." >&2
+ # Show a short summary to aid debugging
+ if [[ -n "$act" ]]; then
+ echo "--- Recent activity snapshot ---"
+ jq -r '.. | objects? | select(has("severity") or has("name")) | "\(.severity // "")\t\(.name // "")\t\(.message // "")"' <<<"$act" | tail -n 20 || true
+ fi
+ return 1
+ fi
+
+ sleep "$interval_s"
+ done
+}
+
+repos_json="$(nexusApi GET '/staging/profile_repositories')"
+repoId="$(jq -r --arg d "${STAGING_DESCRIPTION}" '.data[] | select(.description==$d) | .repositoryId' <<<"${repos_json}")"
+profileId="$(jq -r --arg d "${STAGING_DESCRIPTION}" '.data[] | select(.description==$d) | .profileId' <<<"${repos_json}")"
+state="$(jq -r --arg d "${STAGING_DESCRIPTION}" '.data[] | select(.description==$d) | .type' <<<"${repos_json}")"
+
+if [[ -z "${repoId}" || -z "${profileId}" ]]; then
+ echo "ERROR: No staged repository found with description: ${STAGING_DESCRIPTION}" >&2
+ exit 2
+fi
+echo "Found staged repo: ${repoId} (profile: ${profileId}, state: ${state})"
+if [[ "${state}" == "open" ]]; then
+ echo "ERROR: Staged Repo is not closed: ${STAGING_DESCRIPTION}" >&2
+ exit 3
+fi
+
+if [[ "${state}" == "closed" ]]; then
+ echo "Promoting (release) ${repoId}âĻ"
+ nexusApi POST "/staging/profiles/${profileId}/promote" \
+ --data "$(jq -n --arg r "${repoId}" --arg d "${STAGING_DESCRIPTION}" '{data:{stagedRepositoryId:$r,description:$d}}')"
+fi
+
+wait_for_promotion "$repoId" 600 3
+
+echo "Dropping staging repository ${repoId}âĻ"
+nexusApi POST "/staging/profiles/${profileId}/drop" \
+ --data "$(jq -n --arg r "$repoId" --arg d "${STAGING_DESCRIPTION}" '{data:{stagedRepositoryId:$r,description:$d}}')"
+
+echo "Done. Released ${repoId}."
\ No newline at end of file
diff --git a/.github/vote_templates/groovy_pmc.txt b/.github/vote_templates/groovy_pmc.txt
index 14a16d2ad5..b205127d9c 100644
--- a/.github/vote_templates/groovy_pmc.txt
+++ b/.github/vote_templates/groovy_pmc.txt
@@ -1,4 +1,5 @@
Hi Everyone,
+
The Apache Grails community has voted to approve the release of Apache Grails ${VERSION}.
As the incubation host, we now kindly request the Groovy PMC to review & approve our ASF release.
diff --git a/.github/vote_templates/staged.txt b/.github/vote_templates/staged.txt
index 63f2f1e575..af21ff9f42 100644
--- a/.github/vote_templates/staged.txt
+++ b/.github/vote_templates/staged.txt
@@ -4,12 +4,7 @@ I am happy to start the VOTE thread for an Apache Grails (incubating) release of
Please note the following build reproducibility issues for this release:
-1. grails-cli-7.0.0-RC1-all.jar # caused by extension module merge from the shadowJar (need to strip date from property file) & the plugin.xml signature is being included
-- Created https://github.com/apache/grails-core/issues/14951 to address this in a future release
-2. grails-converters-7.0.0-RC1.jar # contents identical on decompile
-3. grails-datamapping-core-test-7.0.0-RC1-tests.jar # contents identical on decompile
-4. grails-datamapping-tck-7.0.0-RC1.jar # contents identical on decompile
-5. grails-forge-core-7.0.0-RC1-javadoc.jar # due to groovy 3 in forge, we need to update to groovy 4 to fix this
+1. grails-forge-core-${VERSION}-javadoc.jar # due to groovy 3 in forge, we need to update to groovy 4 to fix this
Given that these differences are known, explainable, and immaterial, we are moving forward with a release vote.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9b8dc39731..9f7f35ecf1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,7 +23,7 @@ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
publish:
- name: "Stage Files"
+ name: "Stage Jar Files"
permissions:
contents: write # to create release & upload wrapper
issues: write # to modify milestones
@@ -361,6 +361,7 @@ jobs:
set -e
gh release --repo ${{ github.repository }} delete-asset v${{ needs.publish.outputs.release_version }} PUBLISHED_ARTIFACTS.txt --yes
upload:
+ name: "Upload Distributions to dist.apache.org"
needs: [ publish, source ]
runs-on: ubuntu-24.04
permissions:
@@ -470,7 +471,7 @@ jobs:
VERSION_COMMIT_ID: ${{ needs.publish.outputs.commit_hash }}
run: |
export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt)
-
+ echo "::group::Grails PPMC Vote Email"
echo "*************************************************"
echo "Subject: [VOTE] Release Apache Grails (incubating) ${VERSION}"
echo "*************************************************"
@@ -478,6 +479,7 @@ jobs:
echo "*************************************************"
cat grails-core/.github/vote_templates/staged.txt | envsubst
echo "*************************************************"
+ echo "::endgroup::"
- name: 'Groovy Vote Email'
env:
VERSION: ${{ needs.publish.outputs.release_version }}
@@ -485,6 +487,7 @@ jobs:
run: |
export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt)
+ echo "::group::Groovy PMC Vote Email"
echo "*************************************************"
echo "Subject: [VOTE] Approval of Apache Grails (incubating) ${VERSION} release by Groovy PMC"
echo "*************************************************"
@@ -492,25 +495,60 @@ jobs:
echo "*************************************************"
cat grails-core/.github/vote_templates/groovy_pmc.txt | envsubst
echo "*************************************************"
- - name: 'Announcement Email'
- env:
- VERSION: ${{ needs.publish.outputs.release_version }}
- VERSION_COMMIT_ID: ${{ needs.publish.outputs.commit_hash }}
- PREVIOUS_VERSION: 'TODO_PREVIOUS_VERSION'
- run: |
- export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt)
-
- echo "*************************************************"
- echo "Subject: [ANNOUNCE] Apache Grails (incubating) ${VERSION}"
- echo "*************************************************"
- echo "Body:"
- echo "*************************************************"
- cat grails-core/.github/vote_templates/announce.txt | envsubst
- echo "*************************************************"
+ echo "::endgroup::"
+ release:
+ environment: release
+ name: "VOTE SUCCEEDED - Release Artifacts"
+ needs: [ publish, source, upload ]
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it
+ run: curl -s https://api.ipify.org
+ - name: "Setup SVN and Tools"
+ run: sudo apt-get install -y subversion subversion-tools tree gettext-base
+ - name: "đŗ Grails PPMC Vote Confirmation - MANUAL"
+ run: |
+ echo "::group::Manual Confirmation"
+ echo "đ This step is a placeholder that the vote confirmation on dev@grails.apache.org completed successfully."
+ echo "::endgroup::"
+ - name: "đŗ Groovy PMC Vote Confirmation - MANUAL"
+ run: |
+ echo "::group::Manual Confirmation"
+ echo "đ This step is a placeholder that the vote confirmation on dev@grails.apache.org completed successfully."
+ echo "::endgroup::"
+ - name: "đ Release JAR files - MANUAL"
+ run: |
+ echo "::group::Manual Jar Promotion"
+ echo "Run .github/scripts/releaseJarFiles.sh 'v${{ needs.publish.outputs.release_version }}' '${{ needs.publish.outputs.extract_repository_name }}:${{ needs.publish.outputs.release_version }}' ASF_USER ASF_PASS"
+ echo "::endgroup::"
+ - name: "đ Release distribution artifacts - MANUAL"
+ run: |
+ echo "::group::Manual ASF Artifact Promotion"
+ echo "Run github/scripts/releaseDistributions.sh 'v${{ needs.publish.outputs.release_version }}' ASF_USER ASF_PASS"
+ echo "::endgroup::"
+ - name: "â
Update ASF Reporter - MANUAL"
+ run: |
+ echo "::group::Manual ASF Reporter Update"
+ TODAY=$(date +"%Y-%m-%d")
+ echo "Check email & update https://reporter.apache.org to mark the release ${{ needs.publish.outputs.release_version }} as complete as of ${TODAY}"
+ echo "Note: this is a place holder; currently this is not possible since Groovy sponsors us instead of the incubator PMC"
+ echo "::endgroup::"
+ - name: "â
Deploy grails-forge - MANUAL"
+ run: |
+ echo "::group::Manual grails-forge deployment"
+ echo "Kick off Forge-* workflow for version ${{ needs.publish.outputs.release_version }} based on the below rules:"
+ cat <<'EOF'
+ * RELEASE - GA releases only
+ * NEXT - Milestones and Release Candidate
+ * SNAPSHOT - current or next version snapshot
+ * PREV - previous release version
+ * PREV-SNAPSHOT - previous version snapshot
+ EOF
+ echo "::endgroup::"
docs:
environment: docs
- name: "Publish Documentation"
- needs: [ publish, source, upload ]
+ name: "VOTE SUCCEEDED - Publish Documentation"
+ needs: [ publish, source, upload ] # TODO Once we have confirmed release won't fail, add it as a dependency here
runs-on: ubuntu-24.04
steps:
- name: "đĨ Checkout repository"
@@ -544,9 +582,9 @@ jobs:
VERSION: ${{ needs.publish.outputs.release_version }}
sdkman:
environment: sdkman
- name: "Release to SDKMAN!"
+ name: "VOTE SUCCEEDED - Release to SDKMAN!"
runs-on: ubuntu-24.04
- needs: [ publish, source, upload ]
+ needs: [ publish, source, upload ] # TODO Once we have confirmed release won't fail, add it as a dependency here
steps:
- name: "đĨ Checkout repository"
uses: actions/checkout@v4
@@ -576,10 +614,10 @@ jobs:
env:
GVM_SDKVENDOR_KEY: ${{ secrets.GVM_SDKVENDOR_KEY }}
GVM_SDKVENDOR_TOKEN: ${{ secrets.GVM_SDKVENDOR_TOKEN }}
- release:
- name: "Close Release"
+ close:
+ name: "VOTE SUCCEEDED - Close Release"
environment: release
- needs: [ publish, source, upload, docs, sdkman ]
+ needs: [ publish, source, upload, docs, sdkman ] # TODO Once we have confirmed release won't fail, add it as a dependency here
runs-on: ubuntu-24.04
permissions:
contents: write # required for gradle.properties revert
@@ -606,3 +644,25 @@ jobs:
uses: apache/grails-github-actions/post-release@asf
env:
RELEASE_SCRIPT_PATH: '.github/scripts/setSnapshotGrailsVersion.sh'
+ - name: 'đ Create Blog Post - MANUAL'
+ run: |
+ echo "::group::Blog Post Creation - MANUAL"
+ echo "Publish a blog post on https://grails.apache.org/blog/ about the new release ${{ needs.publish.outputs.release_version }} using the repo https://github.com/apache/grails-static-website"
+ echo "::endgroup::"
+ - name: 'Announcement Email'
+ env:
+ VERSION: ${{ needs.publish.outputs.release_version }}
+ VERSION_COMMIT_ID: ${{ needs.publish.outputs.commit_hash }}
+ PREVIOUS_VERSION: 'TODO_PREVIOUS_VERSION'
+ run: |
+ export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt)
+
+ echo "::group::Announcement Email"
+ echo "*************************************************"
+ echo "Subject: [ANNOUNCE] Apache Grails (incubating) ${VERSION}"
+ echo "*************************************************"
+ echo "Body:"
+ echo "*************************************************"
+ cat grails-core/.github/vote_templates/announce.txt | envsubst
+ echo "*************************************************"
+ echo "::endgroup::"