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/grails-intellij-plugin.git

commit 60f07431fbfb5c28e28b1de8f537c29c4b60307e
Author: James Daugherty <[email protected]>
AuthorDate: Sat Jul 18 15:37:18 2026 -0400

    Add release workflow and Marketplace signing config
---
 .github/workflows/release.yml | 274 ++++++++++++++++++++++++++++++++++++++++++
 build.gradle.kts              |  11 ++
 2 files changed, 285 insertions(+)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..06569dd
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,274 @@
+# 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.
+
+# ASF policy framing: the official release is the signed source archive, voted 
on by
+# the PMC and published to dist.apache.org; the plugin ZIP on JetBrains 
Marketplace
+# is a convenience binary.
+name: "Release"
+on:
+  release:
+    types: [ published ]
+permissions: { }
+env:
+  DIST_NAME: apache-grails-intellij-plugin
+  JAVA_DISTRIBUTION: liberica
+  JAVA_VERSION: 21
+  PROJECT_NAME: Apache Grails IntelliJ Plugin
+  REPO_SLUG: ${{ github.repository }}
+  SVN_FOLDER: intellij-plugin
+  SVN_PROJECT: grails
+  TAG: ${{ github.event.release.tag_name }}
+  VERSION: will be computed in each job
+concurrency:
+  group: release-pipeline-${{ github.event.release.target_commitish }}
+  cancel-in-progress: false
+jobs:
+  publish:
+    name: "Build & Stage Plugin ZIP"
+    permissions:
+      contents: write # to upload release assets
+    runs-on: ubuntu-latest
+    steps:
+      - name: "📝 Establish release version"
+        run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          ref: ${{ env.TAG }}
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 
v5.2.0
+        with:
+          distribution: ${{ env.JAVA_DISTRIBUTION }}
+          java-version: ${{ env.JAVA_VERSION }}
+      - name: "🐘 Setup Gradle"
+        uses: 
gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          cache-provider: basic # 'basic' uses the MIT-licensed, open-source 
cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle 
commercial Terms of Use)
+      - name: "🏗️ Build & Test"
+        run: ./gradlew check buildPlugin -PpluginVersion=${VERSION}
+      - name: "🔏 Sign plugin ZIP for JetBrains Marketplace"
+        env:
+          CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }}
+          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
+          PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }}
+        run: ./gradlew signPlugin -PpluginVersion=${VERSION}
+      - name: "📦 Rename plugin ZIP to distribution name"
+        run: cp build/distributions/Grails-${VERSION}-signed.zip 
${DIST_NAME}-${VERSION}-bin.zip
+      - name: "🔐 Set up GPG" # use env var to not expose the key
+        env:
+          GPG_KEY: ${{ secrets.GRAILS_GPG_KEY }}
+        run: |
+          echo "${GPG_KEY}" | gpg --batch --import
+          gpg --list-keys
+      - name: "🔏 GPG-sign plugin ZIP"
+        run: >
+          gpg
+          --default-key "${{ secrets.GPG_KEY_ID }}"
+          --batch
+          --yes
+          --pinentry-mode loopback
+          --armor
+          --detach-sign
+          ${DIST_NAME}-${VERSION}-bin.zip
+      - name: "📦 Create plugin ZIP checksum"
+        run: sha512sum ${DIST_NAME}-${VERSION}-bin.zip > 
${DIST_NAME}-${VERSION}-bin.zip.sha512
+      - name: "🚀 Upload plugin ZIP to GitHub Release"
+        uses: 
softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
+        with:
+          tag_name: ${{ env.TAG }}
+          files: |
+            ${{ env.DIST_NAME }}-${{ env.VERSION }}-bin.zip
+            ${{ env.DIST_NAME }}-${{ env.VERSION }}-bin.zip.sha512
+            ${{ env.DIST_NAME }}-${{ env.VERSION }}-bin.zip.asc
+  source:
+    name: "Create Source Distribution"
+    needs: publish
+    permissions:
+      contents: write # to upload release assets
+    runs-on: ubuntu-latest
+    steps:
+      - name: "📝 Establish release version"
+        run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          path: grails-intellij-plugin
+          ref: ${{ env.TAG }}
+      - name: "🗑️ Remove files that must not ship in the source distribution"
+        run: |
+          rm -rf grails-intellij-plugin/.git
+          rm -rf grails-intellij-plugin/.github
+          rm -f grails-intellij-plugin/.asf.yaml
+          rm -f grails-intellij-plugin/gradlew 
grails-intellij-plugin/gradlew.bat
+          rm -rf grails-intellij-plugin/gradle/wrapper
+      - name: "📦 Create source distribution ZIP"
+        run: zip -r ${DIST_NAME}-${VERSION}-src.zip grails-intellij-plugin
+      - name: "🔐 Set up GPG" # use env var to not expose the key
+        env:
+          GPG_KEY: ${{ secrets.GRAILS_GPG_KEY }}
+        run: |
+          echo "${GPG_KEY}" | gpg --batch --import
+          gpg --list-keys
+      - name: "🔏 Sign source distribution ZIP"
+        run: >
+          gpg
+          --default-key "${{ secrets.GPG_KEY_ID }}"
+          --batch
+          --yes
+          --pinentry-mode loopback
+          --armor
+          --detach-sign
+          ${DIST_NAME}-${VERSION}-src.zip
+      - name: "📦 Create source distribution checksum"
+        run: sha512sum ${DIST_NAME}-${VERSION}-src.zip > 
${DIST_NAME}-${VERSION}-src.zip.sha512
+      - name: "🚀 Upload ZIP and Signature to GitHub Release"
+        uses: 
softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
+        with:
+          tag_name: ${{ env.TAG }}
+          files: |
+            ${{ env.DIST_NAME }}-${{ env.VERSION }}-src.zip
+            ${{ env.DIST_NAME }}-${{ env.VERSION }}-src.zip.sha512
+            ${{ env.DIST_NAME }}-${{ env.VERSION }}-src.zip.asc
+  upload:
+    name: "Upload Distributions to dist.apache.org"
+    needs: [ publish, source ]
+    runs-on: ubuntu-latest
+    env:
+      SVN_USERNAME: ${{ secrets.SVC_DIST_GRAILS_USERNAME }}
+      SVN_PASSWORD: ${{ secrets.SVC_DIST_GRAILS_PASSWORD }}
+    steps:
+      - name: "📝 Establish release version"
+        run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
+      - name: "🛠️ Install tools"
+        run: sudo apt-get install -y subversion tree
+      - name: "👀 Ensure dev/${{ env.SVN_PROJECT }}/${{ env.SVN_FOLDER }} 
folder exists"
+        run: |
+          set -e
+          if svn ls 
https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER} --username 
"${SVN_USERNAME}" --password "${SVN_PASSWORD}" --non-interactive; then
+            echo "Dev Folder [${SVN_PROJECT}/${SVN_FOLDER}] already exists — 
skipping creation"
+          else
+            echo "Dev Folder [${SVN_PROJECT}/${SVN_FOLDER}] does not exist, 
creating"
+            svnmucc --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" 
--non-interactive \
+              mkdir 
https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER} \
+              -m "Create '${SVN_PROJECT}/${SVN_FOLDER}' dev folder"
+          fi
+      - name: "📥 Checkout dev repo"
+        run: |
+          svn checkout --username "${SVN_USERNAME}" --password 
"${SVN_PASSWORD}" --non-interactive 
"https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}"; dev-repo
+      - name: "🗑️ Remove existing dev version"
+        run: |
+          cd dev-repo
+          if [ -d "${VERSION}" ]; then
+            svn delete "${VERSION}"
+            svn commit -m "Remove ${SVN_PROJECT} ${SVN_FOLDER} dev version 
${VERSION}" --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" 
--non-interactive
+          else
+            echo "No existing dev version ${VERSION} to remove"
+          fi
+      - name: "📥 Fetch source distribution"
+        run: |
+          cd dev-repo
+          mkdir -p ${VERSION}/sources
+          cd ${VERSION}/sources
+          curl -f -LO 
https://github.com/${REPO_SLUG}/releases/download/${TAG}/${DIST_NAME}-${VERSION}-src.zip
+          curl -f -LO 
https://github.com/${REPO_SLUG}/releases/download/${TAG}/${DIST_NAME}-${VERSION}-src.zip.sha512
+          curl -f -LO 
https://github.com/${REPO_SLUG}/releases/download/${TAG}/${DIST_NAME}-${VERSION}-src.zip.asc
+          ls -l
+      - name: "📥 Fetch binary distribution"
+        run: |
+          cd dev-repo
+          mkdir -p ${VERSION}/distribution
+          cd ${VERSION}/distribution
+          curl -f -LO 
https://github.com/${REPO_SLUG}/releases/download/${TAG}/${DIST_NAME}-${VERSION}-bin.zip
+          curl -f -LO 
https://github.com/${REPO_SLUG}/releases/download/${TAG}/${DIST_NAME}-${VERSION}-bin.zip.sha512
+          curl -f -LO 
https://github.com/${REPO_SLUG}/releases/download/${TAG}/${DIST_NAME}-${VERSION}-bin.zip.asc
+          ls -l
+      - name: "🚀 Upload distributions"
+        run: |
+          cd dev-repo
+          echo "Adding the following files to SVN:"
+          tree
+          svn add ${VERSION} --force
+          svn commit -m "Upload ${PROJECT_NAME} distribution files for 
${VERSION}" --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" 
--non-interactive
+      - name: "🗳 MANUAL - Start PMC Vote"
+        run: |
+          echo "::group::Manual Vote"
+          echo "Distributions staged at 
https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}";
+          echo "Start a [VOTE] thread for ${PROJECT_NAME} ${VERSION} on 
[email protected]."
+          echo "::endgroup::"
+  release:
+    environment: release # post-vote gate: required reviewers configured in 
.asf.yaml
+    name: "VOTE SUCCEEDED - Publish Release"
+    needs: [ publish, source, upload ]
+    runs-on: ubuntu-latest
+    env:
+      SVN_USERNAME: ${{ secrets.SVC_DIST_GRAILS_USERNAME }}
+      SVN_PASSWORD: ${{ secrets.SVC_DIST_GRAILS_PASSWORD }}
+    steps:
+      - name: "📝 Establish release version"
+        run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
+      - name: "🗳 MANUAL - Confirm Grails PMC Vote succeeded"
+        run: |
+          echo "::group::Manual Confirmation"
+          echo "🔎 Make sure that the vote confirmation on 
[email protected] completed successfully before proceeding."
+          echo "::endgroup::"
+      - name: "🛠️ Install tools"
+        run: sudo apt-get install -y subversion
+      - name: "🚀 Promote distributions dev -> release"
+        run: |
+          set -e
+          if svn ls 
https://dist.apache.org/repos/dist/release/${SVN_PROJECT}/${SVN_FOLDER} 
--username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" --non-interactive; 
then
+            echo "Release folder exists"
+          else
+            svnmucc --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" 
--non-interactive \
+              mkdir 
https://dist.apache.org/repos/dist/release/${SVN_PROJECT}/${SVN_FOLDER} \
+              -m "Create '${SVN_PROJECT}/${SVN_FOLDER}' release folder"
+          fi
+          svnmucc --username "${SVN_USERNAME}" --password "${SVN_PASSWORD}" 
--non-interactive \
+            mv 
https://dist.apache.org/repos/dist/dev/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION} \
+               
https://dist.apache.org/repos/dist/release/${SVN_PROJECT}/${SVN_FOLDER}/${VERSION}
 \
+            -m "Release ${PROJECT_NAME} ${VERSION}"
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+        with:
+          ref: ${{ env.TAG }}
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 
v5.2.0
+        with:
+          distribution: ${{ env.JAVA_DISTRIBUTION }}
+          java-version: ${{ env.JAVA_VERSION }}
+      - name: "🐘 Setup Gradle"
+        uses: 
gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          cache-provider: basic # see note above
+      - name: "🚀 Publish to JetBrains Marketplace"
+        env:
+          CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }}
+          PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
+          PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }}
+          PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
+        run: ./gradlew publishPlugin -PpluginVersion=${VERSION}
+      - name: "✅ MANUAL - Update ASF Reporter"
+        run: |
+          echo "::group::Manual ASF Reporter Update"
+          TODAY=$(date +"%Y-%m-%d")
+          echo "Update 
https://reporter.apache.org/addrelease.html?${SVN_PROJECT} to add"
+          echo "INTELLIJ-PLUGIN-${VERSION} as complete as of ${TODAY}"
+          echo "::endgroup::"
+      - name: "📧 MANUAL - Send Vote Result & Announcement Emails"
+        run: |
+          echo "::group::Manual Emails"
+          echo "Send [RESULT][VOTE] to [email protected] and"
+          echo "[ANNOUNCE] ${PROJECT_NAME} ${VERSION} to [email protected], 
[email protected], [email protected]."
+          echo "::endgroup::"
diff --git a/build.gradle.kts b/build.gradle.kts
index f2ecc9a..dd8c831 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -128,6 +128,17 @@ intellijPlatform {
             recommended()
         }
     }
+
+    // JetBrains Marketplace code signing / publishing; secrets provided by 
the release workflow
+    signing {
+        certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN")
+        privateKey = providers.environmentVariable("PRIVATE_KEY")
+        password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
+    }
+
+    publishing {
+        token = providers.environmentVariable("PUBLISH_TOKEN")
+    }
 }
 
 tasks {

Reply via email to