This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-grails-gradle-publish.git


The following commit(s) were added to refs/heads/main by this push:
     new f1106b4  chore: add release helper scripts for post voting
f1106b4 is described below

commit f1106b406efc2fe4a1a12c3cce23bde68adbd486
Author: James Daugherty <[email protected]>
AuthorDate: Thu Sep 11 14:33:21 2025 -0400

    chore: add release helper scripts for post voting
---
 .asf.yaml                               |   7 ++
 .github/scripts/releaseDistributions.sh |  83 +++++++++++++++++++
 .github/scripts/releaseJarFiles.sh      | 141 ++++++++++++++++++++++++++++++++
 .github/workflows/release.yaml          |  89 +++++++++++++++-----
 4 files changed, 299 insertions(+), 21 deletions(-)

diff --git a/.asf.yaml b/.asf.yaml
index fc165e8..6a1d253 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -27,6 +27,13 @@ github:
         - id: jdaugherty
           type: User
       wait_timer: 0
+    close:
+      required_reviewers:
+        - id: grails-committers
+          type: Team
+        - id: jdaugherty
+          type: User
+      wait_timer: 0
 notifications:
   # GitHub related
   commits: [email protected]
diff --git a/.github/scripts/releaseDistributions.sh 
b/.github/scripts/releaseDistributions.sh
new file mode 100755
index 0000000..08da0e9
--- /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/grails-publish";
+DEV_ROOT="https://dist.apache.org/repos/dist/dev/incubator/grails/grails-publish";
+
+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 0000000..8421782
--- /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/workflows/release.yaml b/.github/workflows/release.yaml
index c5b8052..72673ae 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -22,7 +22,7 @@ env:
   GRAILS_PUBLISH_RELEASE: 'true'
 jobs:
   publish:
-    name: "Stage Artifacts for Release"
+    name: "Stage Jar Files"
     permissions:
       contents: write # to stage distributions to the GitHub release page
       issues: write # to modify milestones
@@ -346,7 +346,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) - Gradle 
Plugin - Grails Publish ${VERSION}"
           echo "*************************************************"
@@ -354,6 +354,7 @@ jobs:
           echo "*************************************************"
           cat grails-publish/.github/vote_templates/staged.txt | envsubst
           echo "*************************************************"
+          echo "::endgroup::"
       - name: '📧 Print Groovy Vote Email'
         env:
           VERSION: ${{ needs.publish.outputs.release_version }}
@@ -361,6 +362,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) - 
Gradle Plugin - Grails Publish ${VERSION} release by Groovy PMC"
           echo "*************************************************"
@@ -368,25 +370,48 @@ jobs:
           echo "*************************************************"
           cat grails-publish/.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'
+          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: |
-          export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' 
dev-repo/DIST_SVN_REVISION.txt)
-          
-          echo "*************************************************"
-          echo "Subject: [ANNOUNCE] Apache Grails (incubating) - Gradle Plugin 
- Grails Publish ${VERSION}"
-          echo "*************************************************"
-          echo "Body:"
-          echo "*************************************************"
-          cat grails-publish/.github/vote_templates/announce.txt | envsubst
-          echo "*************************************************"
+          echo "::group::Manual Confirmation"
+          echo "🔎 This step is a placeholder that the vote confirmation on 
[email protected] 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 
[email protected] 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::"
   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"
@@ -417,10 +442,10 @@ jobs:
           GRADLE_PUBLISH_RELEASE: 'true'
           SOURCE_FOLDER: plugin/build/docs/api
           VERSION: ${{ needs.publish.outputs.release_version }}
-  release:
-    name: "Close Release"
+  close:
+    name: "VOTE SUCCEEDED - Close Release"
     environment: release
-    needs: [ publish, source, upload, docs ]
+    needs: [ publish, source, upload, docs ] # TODO Once we have confirmed 
release won't fail, add `release` as a dependency here
     runs-on: ubuntu-24.04
     permissions:
       contents: write # required for gradle.properties revert
@@ -445,3 +470,25 @@ jobs:
           develocity-access-key: ${{ secrets.GRAILS_DEVELOCITY_ACCESS_KEY }}
       - name: "âš™ī¸ Run post-release"
         uses: apache/grails-github-actions/post-release@asf
+      - 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) - Gradle Plugin 
- Grails Publish ${VERSION}"
+          echo "*************************************************"
+          echo "Body:"
+          echo "*************************************************"
+          cat grails-publish/.github/vote_templates/announce.txt | envsubst
+          echo "*************************************************"
+          echo "::endgroup::"

Reply via email to