|
| 1 | +# Copyright (c) JFrog Ltd. 2026 |
| 2 | +# Licensed under the Apache License, Version 2.0 |
| 3 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 4 | +# |
| 5 | +# Release flow: a human bumps VERSION and gradle.properties together, merges to main, |
| 6 | +# then tags that main commit: |
| 7 | +# git checkout main && git pull && git tag vX.Y.Z && git push origin vX.Y.Z |
| 8 | +# This workflow builds the plugin zip and attaches it to a GitHub Release. |
| 9 | +name: Release |
| 10 | + |
| 11 | +on: |
| 12 | + push: |
| 13 | + tags: ["v*"] |
| 14 | + |
| 15 | +permissions: |
| 16 | + contents: write |
| 17 | + |
| 18 | +jobs: |
| 19 | + release: |
| 20 | + runs-on: ubuntu-latest |
| 21 | + steps: |
| 22 | + # Full history, so the tag checks below can see existing tags and ancestry. |
| 23 | + - uses: actions/checkout@v5 |
| 24 | + with: |
| 25 | + fetch-depth: 0 |
| 26 | + |
| 27 | + # Refuse to release a tag that points at a commit not on main (e.g. a feature branch). |
| 28 | + - name: Ensure the tag is on main |
| 29 | + run: | |
| 30 | + git fetch --no-tags origin main |
| 31 | + if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then |
| 32 | + echo "::error::Tag ${GITHUB_REF_NAME} points at a commit that is not on main. Release only after merging to main." |
| 33 | + exit 1 |
| 34 | + fi |
| 35 | +
|
| 36 | + # The tag is pushed by a human before this workflow ever runs, so by the time any check here |
| 37 | + # could fail, the tag already exists. Catch the two ways that goes wrong: a malformed tag name |
| 38 | + # (the trigger glob `v*` accepts `v1`, `v1.2.3-rc1`, `vfoo`, not just `vX.Y.Z`), and a tag that |
| 39 | + # is not actually newer than the last release (a typo'd or duplicate version number). |
| 40 | + # `sort -V` orders numerically (0.9.0 < 0.10.0), not lexicographically. |
| 41 | + - name: Validate the tag itself (format, and newer than the last release) |
| 42 | + run: | |
| 43 | + set -euo pipefail |
| 44 | + TAG="${GITHUB_REF_NAME}" |
| 45 | + if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then |
| 46 | + echo "::error::Tag '$TAG' is not a valid vX.Y.Z semver tag — refusing to release" |
| 47 | + exit 1 |
| 48 | + fi |
| 49 | + git fetch --tags origin |
| 50 | + # grep -v exits 1 if it filters out every line - the first release, where the only tag is |
| 51 | + # the one being pushed. Absorb it so the "no previous release" check below still runs. |
| 52 | + LATEST=$(git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | { grep -v "^${TAG}\$" || true; } | sort -V | tail -1) |
| 53 | + if [ -n "$LATEST" ]; then |
| 54 | + LOWEST=$(printf '%s\n%s\n' "$TAG" "$LATEST" | sort -V | head -1) |
| 55 | + if [ "$LOWEST" = "$TAG" ]; then |
| 56 | + echo "::error::Tag $TAG is not newer than the latest release ($LATEST) — check for a typo" |
| 57 | + exit 1 |
| 58 | + fi |
| 59 | + fi |
| 60 | +
|
| 61 | + # VERSION at the repo root is the single source of truth for the version; the tag has to |
| 62 | + # agree with it, and gradle.properties has to agree with VERSION. |
| 63 | + - name: Ensure the tag matches VERSION, and VERSION matches gradle.properties |
| 64 | + run: | |
| 65 | + set -euo pipefail |
| 66 | + FILE_VERSION=$(tr -d '[:space:]' < VERSION) |
| 67 | + if [ "${GITHUB_REF_NAME#v}" != "$FILE_VERSION" ]; then |
| 68 | + echo "::error::Tag ${GITHUB_REF_NAME} does not match VERSION ($FILE_VERSION) — bump VERSION and re-tag" |
| 69 | + exit 1 |
| 70 | + fi |
| 71 | + GRADLE_VERSION=$(grep -E '^version[[:space:]]*=' gradle.properties | sed -E 's/^version[[:space:]]*=[[:space:]]*//') |
| 72 | + if [ "$FILE_VERSION" != "$GRADLE_VERSION" ]; then |
| 73 | + echo "::error::VERSION ($FILE_VERSION) does not match gradle.properties version ($GRADLE_VERSION) — sync them and re-tag" |
| 74 | + exit 1 |
| 75 | + fi |
| 76 | +
|
| 77 | + - name: Set up Node.js |
| 78 | + uses: actions/setup-node@v5 |
| 79 | + with: |
| 80 | + node-version: "24" |
| 81 | + |
| 82 | + # Gate the release on the same layout check PRs run, so a tag can never ship a broken layout. |
| 83 | + - name: Validate plugin layout |
| 84 | + run: node scripts/validate-jetbrains-plugin.mjs |
| 85 | + |
| 86 | + - name: Set up JDK |
| 87 | + uses: actions/setup-java@v4 |
| 88 | + with: |
| 89 | + distribution: temurin |
| 90 | + java-version: "17" |
| 91 | + |
| 92 | + - name: Build plugin |
| 93 | + run: ./gradlew buildPlugin --no-daemon |
| 94 | + |
| 95 | + - name: Verify plugin |
| 96 | + run: ./gradlew verifyPlugin --no-daemon |
| 97 | + |
| 98 | + # buildPlugin names the zip after the plugin and its version, so find it rather than |
| 99 | + # hardcoding the name. |
| 100 | + - name: Locate the built plugin zip |
| 101 | + id: artifact |
| 102 | + run: | |
| 103 | + set -euo pipefail |
| 104 | + ZIP=$(find build/distributions -maxdepth 1 -name '*.zip' | head -1) |
| 105 | + if [ -z "$ZIP" ]; then |
| 106 | + echo "::error::No plugin zip found under build/distributions" |
| 107 | + exit 1 |
| 108 | + fi |
| 109 | + echo "zip=$ZIP" >> "$GITHUB_OUTPUT" |
| 110 | +
|
| 111 | + - name: Create GitHub release |
| 112 | + env: |
| 113 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 114 | + run: | |
| 115 | + gh release create "${{ github.ref_name }}" \ |
| 116 | + "${{ steps.artifact.outputs.zip }}" \ |
| 117 | + --title "${{ github.ref_name }}" \ |
| 118 | + --generate-notes |
| 119 | +
|
| 120 | + # The tag was already pushed (and publicly visible) before this job even started, so a |
| 121 | + # failure above doesn't stop it from existing — it just stops a GitHub Release from being |
| 122 | + # created for it. Delete the tag on any failure so a broken/untested tag doesn't linger as |
| 123 | + # a plausible-looking release; re-tag after fixing the underlying issue. `if: failure()` |
| 124 | + # runs regardless of which step above failed. |
| 125 | + - name: Delete the tag if the release failed |
| 126 | + if: failure() |
| 127 | + run: | |
| 128 | + echo "::error::Release failed — deleting tag ${GITHUB_REF_NAME} so it can't be mistaken for a real release. Fix the issue and re-tag." |
| 129 | + git push origin ":refs/tags/${GITHUB_REF_NAME}" |
0 commit comments