Release #25
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |