Skip to content

Release

Release #25

Workflow file for this run

name: Release
# Requires "Allow auto-merge" enabled in repository settings:
# Settings → General → Pull Requests → Allow auto-merge
on:
workflow_dispatch:
inputs:
version_strategy:
description: How to update version before release (none = use current build.clj/README).
type: choice
required: true
default: none
options:
- none
- sync-upstream
- bump-clj-patch
- set-version
upstream_version:
description: Required for sync-upstream; 3-segment upstream version like 0.1.23.
type: string
required: false
explicit_version:
description: Required for set-version; full 4-segment version, optional -SNAPSHOT.
type: string
required: false
snapshot:
description: Append -SNAPSHOT for sync-upstream/bump-clj-patch (ignored otherwise).
type: boolean
required: false
default: false
permissions:
contents: write
pull-requests: write
id-token: write
attestations: write
jobs:
release:
if: github.actor == 'krukow'
runs-on: ubuntu-latest
environment: release
timeout-minutes: 30
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
- name: Set up Clojure
uses: DeLaGuardo/setup-clojure@13.5.2
with:
cli: latest
bb: 1.12.214
- name: Cache Clojure deps
uses: actions/cache@v5
with:
path: |
~/.m2/repository
~/.gitlibs
~/.deps.clj
.cpcache
key: cljdeps-${{ hashFiles('deps.edn', 'bb.edn') }}
restore-keys: cljdeps-
- name: Validate release inputs
env:
VERSION_STRATEGY: ${{ inputs.version_strategy }}
UPSTREAM_VERSION: ${{ inputs.upstream_version }}
EXPLICIT_VERSION: ${{ inputs.explicit_version }}
SNAPSHOT: ${{ inputs.snapshot }}
run: |
set -euo pipefail
case "$VERSION_STRATEGY" in
none)
if [[ -n "$UPSTREAM_VERSION" || -n "$EXPLICIT_VERSION" ]]; then
echo "upstream_version/explicit_version must be empty when version_strategy=none." >&2
exit 1
fi
if [[ "$SNAPSHOT" == "true" ]]; then
echo "snapshot cannot be true when version_strategy=none." >&2
exit 1
fi
;;
sync-upstream)
if [[ -z "$UPSTREAM_VERSION" ]]; then
echo "upstream_version is required when version_strategy=sync-upstream." >&2
exit 1
fi
if [[ -n "$EXPLICIT_VERSION" ]]; then
echo "explicit_version must be empty when version_strategy=sync-upstream." >&2
exit 1
fi
if [[ ! "$UPSTREAM_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "upstream_version must match X.Y.Z (3 segments)." >&2
exit 1
fi
;;
bump-clj-patch)
if [[ -n "$UPSTREAM_VERSION" || -n "$EXPLICIT_VERSION" ]]; then
echo "upstream_version/explicit_version must be empty when version_strategy=bump-clj-patch." >&2
exit 1
fi
;;
set-version)
if [[ -z "$EXPLICIT_VERSION" ]]; then
echo "explicit_version is required when version_strategy=set-version." >&2
exit 1
fi
if [[ -n "$UPSTREAM_VERSION" ]]; then
echo "upstream_version must be empty when version_strategy=set-version." >&2
exit 1
fi
if [[ ! "$EXPLICIT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$ ]]; then
echo "explicit_version must match X.Y.Z.N or X.Y.Z.N-SNAPSHOT." >&2
exit 1
fi
if [[ "$SNAPSHOT" == "true" ]]; then
echo "snapshot is not used with set-version; include -SNAPSHOT in explicit_version." >&2
exit 1
fi
;;
*)
echo "Unknown version_strategy: $VERSION_STRATEGY" >&2
exit 1
;;
esac
- name: Update version
if: ${{ inputs.version_strategy != 'none' }}
env:
VERSION_STRATEGY: ${{ inputs.version_strategy }}
UPSTREAM_VERSION: ${{ inputs.upstream_version }}
EXPLICIT_VERSION: ${{ inputs.explicit_version }}
SNAPSHOT: ${{ inputs.snapshot }}
run: |
set -euo pipefail
args=()
if [[ "$SNAPSHOT" == "true" ]]; then
args+=(":snapshot" "true")
fi
case "$VERSION_STRATEGY" in
sync-upstream)
clojure -T:build sync-version :upstream "\"${UPSTREAM_VERSION}\"" "${args[@]}"
;;
bump-clj-patch)
clojure -T:build bump-version "${args[@]}"
;;
set-version)
clojure -T:build bump-version :version "\"${EXPLICIT_VERSION}\""
;;
esac
- name: Configure git author
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Update README SHA
run: bb readme:sha
- name: Stamp changelog
run: clojure -T:build stamp-changelog
# --- PR flow: branch protection requires changes go through a PR ---
- name: Create release PR
id: release-pr
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -euo pipefail
if git diff --quiet; then
echo "No changes to commit — skipping PR."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
VERSION=$(grep -m1 -F '(def version' build.clj | sed 's/.*"\(.*\)".*/\1/')
BRANCH="release/v${VERSION}-run${GITHUB_RUN_ID}"
git checkout -b "$BRANCH"
git add build.clj README.md CHANGELOG.md
git commit -m "chore: release v${VERSION}"
git push origin "$BRANCH"
PR_URL=$(gh pr create \
--title "chore: release v${VERSION}" \
--body "Automated release PR for v${VERSION}. Created by the Release workflow (run ${GITHUB_RUN_ID})." \
--base main \
--head "$BRANCH")
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
gh pr merge --auto --squash --delete-branch "$PR_URL"
echo "Auto-merge enabled for $PR_URL"
- name: Wait for release PR to merge
if: steps.release-pr.outputs.skip != 'true'
run: |
set -euo pipefail
PR_URL="${{ steps.release-pr.outputs.pr_url }}"
echo "Waiting for CI + auto-merge on $PR_URL"
for i in $(seq 1 40); do
STATE=$(gh pr view "$PR_URL" --json state -q .state)
case "$STATE" in
MERGED) echo "PR merged!"; exit 0 ;;
CLOSED) echo "::error::PR was closed without merging."; exit 1 ;;
esac
echo " attempt $i/40 — state=$STATE"
sleep 15
done
echo "::error::Timed out (10 min) waiting for PR to merge."
exit 1
- name: Checkout merged main
if: steps.release-pr.outputs.skip != 'true'
run: |
git fetch origin main
git checkout main
git reset --hard origin/main
# --- GPG signing (required for Maven Central) ---
- name: Validate GPG key
run: |
if [[ -z "${{ secrets.GPG_PRIVATE_KEY }}" ]]; then
echo "::error::GPG_PRIVATE_KEY secret is required for Maven Central releases. See PUBLISHING.md."
exit 1
fi
- name: Prepare GPG home
run: |
export GNUPGHOME="$RUNNER_TEMP/gnupg"
mkdir -p "$GNUPGHOME"
chmod 700 "$GNUPGHOME"
- name: Import GPG key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
set -euo pipefail
export GNUPGHOME="$RUNNER_TEMP/gnupg"
# Import key
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import --no-tty --quiet
# Configure agent to accept preset passphrases
printf '%s\n' "default-cache-ttl 7200" "max-cache-ttl 7200" "allow-preset-passphrase" > "$GNUPGHOME/gpg-agent.conf"
gpg-connect-agent "RELOADAGENT" /bye
# Preset passphrase for all keygrips via stdin (avoids exposing in /proc cmdline)
HEX_PASS=$(printf '%s' "${GPG_PASSPHRASE:-}" | xxd -p -c 256 | tr -d '\n' | tr 'a-f' 'A-F')
for KEYGRIP in $(gpg --batch --with-colons --with-keygrip --list-secret-keys | grep '^grp' | cut -d: -f10); do
echo "PRESET_PASSPHRASE $KEYGRIP -1 $HEX_PASS" | gpg-connect-agent
done
# --- Deploy ---
- name: Deploy to Maven Central
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
GNUPGHOME: ${{ runner.temp }}/gnupg
run: |
clojure -T:build deploy-central || {
echo "::group::Deploy error details"
cat /tmp/clojure-*.edn 2>/dev/null || true
echo "::endgroup::"
exit 1
}
# --- Tag + attestation ---
- name: Extract version
id: version
run: |
VERSION=$(grep -m1 -F '(def version' build.clj | sed 's/.*"\(.*\)".*/\1/')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Create release tag
run: |
VERSION="${{ steps.version.outputs.version }}"
git tag -a "v${VERSION}" -m "Release v${VERSION}"
git push origin "v${VERSION}"
- name: Identify release artifacts
id: artifacts
run: |
jar=$(ls target/io.github.copilot-community-sdk/copilot-sdk-clojure-*.jar)
bundle=$(ls target/copilot-sdk-clojure-*-bundle.zip 2>/dev/null || true)
echo "jar=$jar" >> "$GITHUB_OUTPUT"
echo "bundle=$bundle" >> "$GITHUB_OUTPUT"
- name: Attest JAR provenance
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{ steps.artifacts.outputs.jar }}
- name: Attest bundle provenance
if: ${{ steps.artifacts.outputs.bundle != '' }}
uses: actions/attest-build-provenance@v3
with:
subject-path: ${{ steps.artifacts.outputs.bundle }}
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
TAG="v${VERSION}"
ARGS=(
"$TAG"
--title "$TAG"
--generate-notes
)
# Attach artifacts
ARGS+=("${{ steps.artifacts.outputs.jar }}")
if [[ -n "${{ steps.artifacts.outputs.bundle }}" ]]; then
ARGS+=("${{ steps.artifacts.outputs.bundle }}")
fi
# Mark pre-release if SNAPSHOT
if [[ "$VERSION" == *-SNAPSHOT ]]; then
ARGS+=(--prerelease)
fi
gh release create "${ARGS[@]}"
- name: Cleanup GPG home
if: always()
run: rm -rf "$RUNNER_TEMP/gnupg"