From 43435067fd1794f8f70a1f3089bf95acbb4e0be0 Mon Sep 17 00:00:00 2001 From: adityamparikh Date: Tue, 6 Jan 2026 20:39:37 -0500 Subject: [PATCH 1/3] feat: add automated semantic versioning with git-semver-plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add jmongard/git-semver-plugin for automatic version calculation - Version derived from git tags and conventional commits - Add auto-release.yml workflow for automated releases on merge to main - Update build-and-publish.yml to use plugin version - Update WORKFLOWS.md with comprehensive documentation The release flow is now: 1. PR merged β†’ auto-release.yml creates version tag + changelog 2. Tag push β†’ build-and-publish.yml publishes Docker images πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/auto-release.yml | 166 ++++++++++++++ .github/workflows/build-and-publish.yml | 18 +- build.gradle.kts | 16 +- dev-docs/WORKFLOWS.md | 275 ++++++++++++++++++++---- gradle/libs.versions.toml | 4 +- 5 files changed, 424 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/auto-release.yml diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..3e184d3 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,166 @@ +# 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 +# +# http://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. + +# ============================================================================ +# AUTOMATED RELEASE WORKFLOW +# ============================================================================ +# +# PURPOSE: +# -------- +# Automatically creates releases when PRs are merged to main. +# Uses git-semver-plugin to calculate versions from conventional commits. +# +# HOW IT WORKS: +# ------------- +# 1. Triggered when a PR is merged to main +# 2. Calculates next version from conventional commits (feat:, fix:, etc.) +# 3. Generates CHANGELOG.md from commit messages +# 4. Creates release commit and version tag (e.g., v1.0.0) +# 5. Pushes tag to trigger release-publish.yml +# +# VERSION BUMPING (Conventional Commits): +# --------------------------------------- +# - fix: -> patch bump (1.0.0 -> 1.0.1) +# - feat: -> minor bump (1.0.0 -> 1.1.0) +# - feat!: or BREAKING CHANGE -> major bump (1.0.0 -> 2.0.0) +# +# RELATED WORKFLOWS: +# ------------------ +# - build-and-publish.yml: Builds SNAPSHOT on PRs +# - release-publish.yml: Publishes Docker images on tag push + +name: Auto Release + +on: + push: + branches: + - main + +permissions: + contents: write + +env: + JAVA_VERSION: '25' + JAVA_DISTRIBUTION: 'temurin' + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + # Only run if this is a merge commit (not direct push) + # This prevents releases on direct commits to main + if: github.event.head_commit.message != '' && !startsWith(github.event.head_commit.message, 'chore(release):') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history needed for version calculation + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Calculate version + id: version + run: | + VERSION=$(./gradlew printVersion --quiet) + echo "current=$VERSION" >> $GITHUB_OUTPUT + echo "Current version: $VERSION" + + - name: Check if release needed + id: check + run: | + # Check if current version is a SNAPSHOT (needs release) + VERSION="${{ steps.version.outputs.current }}" + if [[ "$VERSION" == *"-SNAPSHOT"* ]]; then + echo "needs_release=true" >> $GITHUB_OUTPUT + # Extract the release version (remove -SNAPSHOT suffix) + RELEASE_VERSION=$(echo "$VERSION" | sed 's/-SNAPSHOT.*//') + echo "release_version=$RELEASE_VERSION" >> $GITHUB_OUTPUT + echo "Will release version: $RELEASE_VERSION" + else + echo "needs_release=false" >> $GITHUB_OUTPUT + echo "Already at release version, skipping" + fi + + - name: Generate changelog + if: steps.check.outputs.needs_release == 'true' + run: | + ./gradlew printChangeLog --quiet > CHANGELOG_NEW.md + if [ -f CHANGELOG.md ]; then + # Prepend new changelog to existing + cat CHANGELOG_NEW.md CHANGELOG.md > CHANGELOG_COMBINED.md + mv CHANGELOG_COMBINED.md CHANGELOG.md + else + mv CHANGELOG_NEW.md CHANGELOG.md + fi + rm -f CHANGELOG_NEW.md + cat CHANGELOG.md + + - name: Configure git + if: steps.check.outputs.needs_release == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create release + if: steps.check.outputs.needs_release == 'true' + run: | + # Add changelog changes + git add CHANGELOG.md + + # Create release using git-semver-plugin + # This creates the release commit and tag + ./gradlew releaseVersion --no-commit + + # Commit changelog with release + git commit -m "chore(release): release version ${{ steps.check.outputs.release_version }}" + + # Create the tag + git tag "v${{ steps.check.outputs.release_version }}" + + - name: Push release + if: steps.check.outputs.needs_release == 'true' + run: | + git push origin main + git push origin "v${{ steps.check.outputs.release_version }}" + + - name: Create GitHub Release + if: steps.check.outputs.needs_release == 'true' + uses: softprops/action-gh-release@v2 + with: + tag_name: "v${{ steps.check.outputs.release_version }}" + name: "Release ${{ steps.check.outputs.release_version }}" + body_path: CHANGELOG.md + draft: false + prerelease: false + + - name: Summary + if: steps.check.outputs.needs_release == 'true' + run: | + echo "### Release Created" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.check.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** v${{ steps.check.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The release-publish workflow will now publish Docker images." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index eacba70..beaf99a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -127,9 +127,12 @@ jobs: runs-on: ubuntu-latest steps: - # Checkout the repository code + # Checkout the repository code with full history + # Full history is required for git-semver-plugin to calculate version - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 # Set up Java environment using centralized configuration # See .github/actions/setup-java/action.yml to update Java version @@ -207,9 +210,12 @@ jobs: packages: write steps: - # Checkout the repository code + # Checkout the repository code with full history + # Full history is required for git-semver-plugin to calculate version - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 # Set up Java environment using centralized configuration # See .github/actions/setup-java/action.yml to update Java version @@ -218,14 +224,14 @@ jobs: # Extract version and determine image tags # Outputs: - # - version: Project version from build.gradle.kts + # - version: Project version from git-semver-plugin # - tags: Comma-separated list of Docker tags to apply # - is_release: Whether this is a release build (from version tag) - name: Extract metadata id: meta run: | - # Get version from build.gradle.kts - VERSION=$(grep '^version = ' build.gradle.kts | sed 's/version = "\(.*\)"/\1/') + # Get version from git-semver-plugin (derived from git tags + commits) + VERSION=$(./gradlew printVersion --quiet) echo "version=$VERSION" >> $GITHUB_OUTPUT # Determine image tags based on trigger type @@ -235,7 +241,7 @@ jobs: echo "tags=$TAG_VERSION,latest" >> $GITHUB_OUTPUT echo "is_release=true" >> $GITHUB_OUTPUT else - # For main branch, append short commit SHA for traceability + # For main branch, use SNAPSHOT version with short commit SHA SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) echo "tags=$VERSION-$SHORT_SHA,latest" >> $GITHUB_OUTPUT echo "is_release=false" >> $GITHUB_OUTPUT diff --git a/build.gradle.kts b/build.gradle.kts index 8f2b971..ff35805 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,10 +26,24 @@ plugins { alias(libs.plugins.errorprone) alias(libs.plugins.spotless) alias(libs.plugins.jib) + alias(libs.plugins.git.semver) } group = "org.apache.solr" -version = "1.0.0-SNAPSHOT" +// Version is automatically derived from git tags and conventional commits +// Run ./gradlew printVersion to see the current version +// Run ./gradlew releaseVersion to create a release tag + +semver { + // Use "SNAPSHOT" suffix for non-release builds + defaultPreRelease = "SNAPSHOT" + // Tag format: v1.0.0 + releaseTagNameFormat = "v%s" + // Release commit message format + releaseCommitTextFormat = "chore(release): release version %s" +} + +version = semver.version java { toolchain { diff --git a/dev-docs/WORKFLOWS.md b/dev-docs/WORKFLOWS.md index e988d3d..e036019 100644 --- a/dev-docs/WORKFLOWS.md +++ b/dev-docs/WORKFLOWS.md @@ -7,11 +7,52 @@ This guide explains when and how to use each GitHub Actions workflow in the proj | Workflow | Purpose | Trigger | Status | Use For | |------------------------------------------------|-----------------------|----------------------|------------|------------------------| | [build-and-publish.yml](#build-and-publishyml) | Development CI/CD | Automatic (push/PR) | βœ… Active | Daily development | +| [auto-release.yml](#auto-releaseyml) | Automated releases | Automatic (merge) | βœ… Active | Version tagging | | [release-publish.yml](#release-publishyml) | Official ASF releases | Manual (after vote) | βœ… Active | Production releases | | [nightly-build.yml](#nightly-buildyml) | Nightly builds | Scheduled (2 AM UTC) | βœ… Active | Latest unstable builds | | [atr-release-test.yml](#atr-release-testyml) | ATR testing | Manual (safe mode) | βœ… Ready | Testing ATR workflow | | [atr-release.yml](#atr-releaseyml) | ATR production | Manual (blocked) | ⚠️ Blocked | Future ATR releases | +## Semantic Versioning + +This project uses the [git-semver-plugin](https://github.com/jmongard/Git.SemVersioning.Gradle) for automatic version management based on [Conventional Commits](https://www.conventionalcommits.org/). + +### How Versioning Works + +``` +git tag v1.0.0 + β”‚ + β”œβ”€β”€ fix: handle null values β†’ patch bump (1.0.1) + β”œβ”€β”€ feat: add new search filter β†’ minor bump (1.1.0) + └── feat!: breaking API change β†’ major bump (2.0.0) + +Current version: 1.1.0-SNAPSHOT (calculated from commits) +``` + +### Version Commands + +```bash +# Check current calculated version +./gradlew printVersion + +# View changelog from commits +./gradlew printChangeLog + +# Create a release (tag + commit) +./gradlew releaseVersion +``` + +### Commit Message Format + +Use conventional commit prefixes to control version bumps: + +| Prefix | Version Bump | Example | +|--------|--------------|---------| +| `fix:` | Patch (0.0.X) | `fix: handle null pointer in search` | +| `feat:` | Minor (0.X.0) | `feat: add faceted search support` | +| `feat!:` or `BREAKING CHANGE:` | Major (X.0.0) | `feat!: redesign query API` | +| `docs:`, `chore:`, `test:`, `ci:` | No bump | `docs: update README` | + ## Decision Tree: Which Workflow Should I Use? ``` @@ -19,37 +60,38 @@ This guide explains when and how to use each GitHub Actions workflow in the proj β”‚ START: What do you need to do? β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚ β”‚ β”‚ - β–Ό β–Ό β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Develop β”‚ β”‚ Release β”‚ β”‚ Test β”‚ -β”‚ Code β”‚ β”‚ Official β”‚ β”‚ ATR β”‚ -β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ - β”‚ β”‚ β”‚ - β”‚ β”‚ β”‚ - β–Ό β–Ό β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚build-and- β”‚ β”‚ release- β”‚ β”‚atr- β”‚ -β”‚publish.yml β”‚ β”‚ publish β”‚ β”‚release- β”‚ -β”‚ β”‚ β”‚ .yml β”‚ β”‚test.yml β”‚ -β”‚βœ… Automatic β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ on push β”‚ β”‚βœ… Manual β”‚ β”‚βœ… Manual β”‚ -β”‚ β”‚ β”‚after vote β”‚ β”‚safe mode β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Develop β”‚ β”‚ Release β”‚ β”‚ Official β”‚ β”‚ Test β”‚ +β”‚ Code β”‚ β”‚ Version β”‚ β”‚ ASF Rel β”‚ β”‚ ATR β”‚ +β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚build-and- β”‚ β”‚auto- β”‚ β”‚ release- β”‚ β”‚atr- β”‚ +β”‚publish.yml β”‚ β”‚release β”‚ β”‚ publish β”‚ β”‚release- β”‚ +β”‚ β”‚ β”‚ .yml β”‚ β”‚ .yml β”‚ β”‚test.yml β”‚ +β”‚βœ… Automatic β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ on PR β”‚ β”‚βœ… Auto on β”‚ β”‚βœ… Manual β”‚ β”‚βœ… Manual β”‚ +β”‚ β”‚ β”‚merge main β”‚ β”‚after vote β”‚ β”‚safe mode β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ - β”‚ After ATR - β”‚ onboarding? + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Creates tag β”‚ + β”‚ v1.0.0 β”‚ + β”‚ + CHANGELOG β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚atr- β”‚ - β”‚release β”‚ - β”‚ .yml β”‚ - β”‚ β”‚ - β”‚βš οΈ Future β”‚ - β”‚ (blocked)β”‚ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚build-and- β”‚ + β”‚publish.yml β”‚ + β”‚(triggered by β”‚ + β”‚ v* tag) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- @@ -131,6 +173,112 @@ gh workflow run build-and-publish.yml --- +### auto-release.yml + +**Purpose**: Automated version tagging and changelog generation on merge to main + +#### When to Use + +- βœ… Automatic on every merge to `main` +- βœ… Creates version tags based on conventional commits +- βœ… Generates and updates CHANGELOG.md +- βœ… Triggers Docker image publishing via build-and-publish.yml + +#### When NOT to Use + +- ❌ This is fully automatic - no manual intervention needed +- ❌ For official ASF releases (use `release-publish.yml` after vote) + +#### Triggers + +```yaml +on: + push: + branches: + - main +``` + +#### What It Does + +1. **Calculates version** from git tags and conventional commits +2. **Generates changelog** from commit messages +3. **Creates release commit** with updated CHANGELOG.md +4. **Creates version tag** (e.g., `v1.0.0`) +5. **Pushes tag** which triggers `build-and-publish.yml` +6. **Creates GitHub Release** with changelog + +#### Version Calculation + +The [git-semver-plugin](https://github.com/jmongard/Git.SemVersioning.Gradle) analyzes commits since the last tag: + +``` +v1.0.0 (last tag) + β”‚ + β”œβ”€β”€ fix: bug fix β†’ 1.0.1 + β”œβ”€β”€ feat: new feature β†’ 1.1.0 + └── feat!: breaking change β†’ 2.0.0 +``` + +#### How It Works + +``` +PR Merged to main + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. Calculate version β”‚ ./gradlew printVersion +β”‚ (from commits) β”‚ β†’ 1.1.0-SNAPSHOT +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 2. Generate changelogβ”‚ ./gradlew printChangeLog +β”‚ (from commits) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 3. Create release β”‚ git tag v1.1.0 +β”‚ commit + tag β”‚ git push --tags +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 4. GitHub Release β”‚ Created automatically +β”‚ with changelog β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ build-and-publish β”‚ Triggered by v* tag +β”‚ publishes Docker β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +#### Example Commit Messages + +```bash +# These trigger version bumps: +git commit -m "feat: add new search filter" # Minor bump +git commit -m "fix: handle null pointer" # Patch bump +git commit -m "feat!: redesign API" # Major bump + +# These don't trigger version bumps: +git commit -m "docs: update README" +git commit -m "chore: update dependencies" +git commit -m "test: add unit tests" +``` + +#### Skipping Releases + +Release commits are automatically skipped to prevent infinite loops: + +```yaml +if: "!startsWith(github.event.head_commit.message, 'chore(release):')" +``` + +--- + ### release-publish.yml **Purpose**: Official Apache Software Foundation release publishing @@ -493,16 +641,18 @@ gh workflow run atr-release.yml \ ## Workflow Comparison Matrix -| Feature | build-and-publish | release-publish | nightly-build | atr-release-test | atr-release | -|----------------------|-------------------|-----------------|--------------------|------------------|-------------| -| **Status** | βœ… Active | βœ… Active | βœ… Active | βœ… Ready | ⚠️ Blocked | -| **Trigger** | Automatic | Manual | Scheduled | Manual | Manual | -| **Docker Namespace** | Personal/GHCR | `apache/*` | `apache/*-nightly` | Test | `apache/*` | -| **MCP Registry** | ❌ No | βœ… Yes | ❌ No | ❌ No | βœ… Yes | -| **ASF Vote** | ❌ Not required | βœ… Required | ❌ Not required | ❌ Not required | βœ… Required | -| **Signing** | ❌ No | ⚠️ Manual | ❌ No | ⚠️ Simulated | βœ… Automated | -| **Production Ready** | ❌ No | βœ… Yes | ❌ No | ❌ No | ⚠️ Future | -| **Can Test Now** | βœ… Yes | βœ… Yes | βœ… Yes | βœ… Yes | ❌ No | +| Feature | build-and-publish | auto-release | release-publish | nightly-build | atr-release-test | atr-release | +|----------------------|-------------------|-----------------|-----------------|--------------------| -----------------|-------------| +| **Status** | βœ… Active | βœ… Active | βœ… Active | βœ… Active | βœ… Ready | ⚠️ Blocked | +| **Trigger** | Automatic | Auto (merge) | Manual | Scheduled | Manual | Manual | +| **Creates Tags** | ❌ No | βœ… Yes | ❌ No | ❌ No | ❌ No | ❌ No | +| **Changelog** | ❌ No | βœ… Yes | ❌ No | ❌ No | ❌ No | ❌ No | +| **Docker Namespace** | Personal/GHCR | N/A | `apache/*` | `apache/*-nightly` | Test | `apache/*` | +| **MCP Registry** | ❌ No | ❌ No | βœ… Yes | ❌ No | ❌ No | βœ… Yes | +| **ASF Vote** | ❌ Not required | ❌ Not required | βœ… Required | ❌ Not required | ❌ Not required | βœ… Required | +| **Signing** | ❌ No | ❌ No | ⚠️ Manual | ❌ No | ⚠️ Simulated | βœ… Automated | +| **Production Ready** | ❌ No | βœ… Yes | βœ… Yes | ❌ No | ❌ No | ⚠️ Future | +| **Can Test Now** | βœ… Yes | βœ… Yes | βœ… Yes | βœ… Yes | βœ… Yes | ❌ No | --- @@ -510,15 +660,41 @@ gh workflow run atr-release.yml \ ### Scenario 1: I merged a PR and want to test the changes -**Use**: `build-and-publish.yml` (automatic) +**Use**: `build-and-publish.yml` + `auto-release.yml` (both automatic) + +```bash +# 1. Merge your PR with conventional commit messages +git merge feature-branch # e.g., "feat: add new search filter" +git push origin main + +# 2. auto-release.yml automatically: +# - Calculates version (e.g., 1.1.0) +# - Generates CHANGELOG.md +# - Creates tag v1.1.0 +# - Creates GitHub Release + +# 3. build-and-publish.yml automatically: +# - Builds Docker image +# - Publishes to ghcr.io/apache/solr-mcp:1.1.0 +``` + +### Scenario 2: I want to create a version release + +**Use**: Automatic via `auto-release.yml` ```bash -# Workflow runs automatically on merge to main -# Find your images at: -# - ghcr.io/apache/solr-mcp:1.0.0-SNAPSHOT-a1b2c3d +# Just merge PRs with conventional commits - releases happen automatically! + +# Example commits that trigger releases: +git commit -m "feat: add faceted search" # β†’ Minor version bump +git commit -m "fix: handle null pointer" # β†’ Patch version bump +git commit -m "feat!: new query API" # β†’ Major version bump + +# Check what version will be released: +./gradlew printVersion ``` -### Scenario 2: I want to create an official release +### Scenario 3: I want to create an official ASF release **Use**: `release-publish.yml` (manual after vote) @@ -535,7 +711,7 @@ gh workflow run release-publish.yml \ -f release_candidate=rc1 ``` -### Scenario 3: I want to test the latest unreleased code +### Scenario 4: I want to test the latest unreleased code **Use**: `nightly-build.yml` (automatic daily) @@ -544,7 +720,7 @@ gh workflow run release-publish.yml \ docker pull apache/solr-mcp-nightly:latest-nightly ``` -### Scenario 4: I want to prepare for ATR +### Scenario 5: I want to prepare for ATR **Use**: `atr-release-test.yml` (manual testing) @@ -554,7 +730,7 @@ gh workflow run atr-release-test.yml \ -f dry_run=true # Safe mode - no uploads ``` -### Scenario 5: I'm ready to use ATR for releases +### Scenario 6: I'm ready to use ATR for releases **Use**: `atr-release.yml` (blocked - see prerequisites) @@ -636,6 +812,11 @@ gh secret set ASF_USERNAME --body "your-asf-id" ## Quick Command Reference ```bash +# Semantic versioning commands (git-semver-plugin) +./gradlew printVersion # Show current calculated version +./gradlew printChangeLog # Show changelog from commits +./gradlew releaseVersion # Create release commit + tag + # Trigger workflows manually gh workflow run build-and-publish.yml gh workflow run release-publish.yml -f release_version=1.0.0 -f release_candidate=rc1 @@ -664,5 +845,5 @@ git push origin :refs/tags/v1.0.0-rc1 # Delete remote --- -**Last Updated**: 2025-01-12 +**Last Updated**: 2026-01-06 **Workflows Version**: Compatible with all workflows as of this date \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd71d2d..f221e87 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ spring-dependency-management = "1.1.7" errorprone-plugin = "4.2.0" jib = "3.4.5" spotless = "7.0.2" +git-semver = "0.18.0" # Main dependencies spring-ai = "1.1.2" @@ -92,4 +93,5 @@ spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" } spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" } errorprone = { id = "net.ltgt.errorprone", version.ref = "errorprone-plugin" } jib = { id = "com.google.cloud.tools.jib", version.ref = "jib" } -spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } \ No newline at end of file +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +git-semver = { id = "com.github.jmongard.git-semver-plugin", version.ref = "git-semver" } \ No newline at end of file From ed0723ca2eaa976cc3928ff75897d6a43e3c3edb Mon Sep 17 00:00:00 2001 From: adityamparikh Date: Wed, 7 Jan 2026 10:14:59 -0500 Subject: [PATCH 2/3] refactor: separate changelog accumulation from release tagging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - auto-release.yml now only builds SNAPSHOT and accumulates changelog - New cut-release.yml for manual release cutting with version tagging - Changelog accumulates under "Unreleased" until release is cut - Added version_override option for first release (1.0.0) - Updated WORKFLOWS.md with complete release flow documentation Release flow: 1. PR merge β†’ SNAPSHOT build + changelog accumulation (auto) 2. Cut release β†’ version tag + finalized changelog (manual) 3. Official ASF β†’ RC tag + vote + apache/* publish (manual) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/auto-release.yml | 182 ++++++++++-------- .github/workflows/cut-release.yml | 232 +++++++++++++++++++++++ build.gradle.kts | 30 ++- dev-docs/WORKFLOWS.md | 293 +++++++++++++++++++++-------- 4 files changed, 569 insertions(+), 168 deletions(-) create mode 100644 .github/workflows/cut-release.yml diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 3e184d3..8e80270 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -14,34 +14,37 @@ # limitations under the License. # ============================================================================ -# AUTOMATED RELEASE WORKFLOW +# SNAPSHOT BUILD & CHANGELOG ACCUMULATION # ============================================================================ # # PURPOSE: # -------- -# Automatically creates releases when PRs are merged to main. -# Uses git-semver-plugin to calculate versions from conventional commits. +# Builds SNAPSHOT artifacts and accumulates changelog entries when PRs are +# merged to main. Does NOT create version tags - use cut-release.yml for that. # # HOW IT WORKS: # ------------- # 1. Triggered when a PR is merged to main -# 2. Calculates next version from conventional commits (feat:, fix:, etc.) -# 3. Generates CHANGELOG.md from commit messages -# 4. Creates release commit and version tag (e.g., v1.0.0) -# 5. Pushes tag to trigger release-publish.yml +# 2. Builds SNAPSHOT artifacts (version calculated from git-semver-plugin) +# 3. Updates CHANGELOG.md with new commits under "Unreleased" section +# 4. Commits changelog updates back to main # -# VERSION BUMPING (Conventional Commits): -# --------------------------------------- -# - fix: -> patch bump (1.0.0 -> 1.0.1) -# - feat: -> minor bump (1.0.0 -> 1.1.0) -# - feat!: or BREAKING CHANGE -> major bump (1.0.0 -> 2.0.0) +# VERSIONING: +# ----------- +# Uses git-semver-plugin to calculate version from conventional commits: +# - fix: β†’ patch bump +# - feat: β†’ minor bump +# - feat!: or BREAKING CHANGE β†’ major bump +# +# Version remains X.Y.Z-SNAPSHOT until cut-release.yml is triggered. # # RELATED WORKFLOWS: # ------------------ -# - build-and-publish.yml: Builds SNAPSHOT on PRs -# - release-publish.yml: Publishes Docker images on tag push +# - build-and-publish.yml: Builds and publishes Docker images +# - cut-release.yml: Creates version tags and releases (manual) +# - release-publish.yml: Official ASF releases (manual, after vote) -name: Auto Release +name: Snapshot Build on: push: @@ -56,18 +59,17 @@ env: JAVA_DISTRIBUTION: 'temurin' jobs: - release: - name: Create Release + snapshot: + name: Build Snapshot & Update Changelog runs-on: ubuntu-latest - # Only run if this is a merge commit (not direct push) - # This prevents releases on direct commits to main - if: github.event.head_commit.message != '' && !startsWith(github.event.head_commit.message, 'chore(release):') + # Skip if this is a release commit or changelog update + if: "!startsWith(github.event.head_commit.message, 'chore(release):') && !startsWith(github.event.head_commit.message, 'chore(changelog):')" steps: - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 # Full history needed for version calculation + fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Set up JDK ${{ env.JAVA_VERSION }} @@ -84,83 +86,99 @@ jobs: id: version run: | VERSION=$(./gradlew printVersion --quiet) - echo "current=$VERSION" >> $GITHUB_OUTPUT - echo "Current version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Current SNAPSHOT version: $VERSION" + + - name: Build SNAPSHOT + run: ./gradlew build - - name: Check if release needed - id: check + - name: Generate changelog entries + id: changelog run: | - # Check if current version is a SNAPSHOT (needs release) - VERSION="${{ steps.version.outputs.current }}" - if [[ "$VERSION" == *"-SNAPSHOT"* ]]; then - echo "needs_release=true" >> $GITHUB_OUTPUT - # Extract the release version (remove -SNAPSHOT suffix) - RELEASE_VERSION=$(echo "$VERSION" | sed 's/-SNAPSHOT.*//') - echo "release_version=$RELEASE_VERSION" >> $GITHUB_OUTPUT - echo "Will release version: $RELEASE_VERSION" + # Get changelog from git-semver-plugin + CHANGELOG_CONTENT=$(./gradlew printChangeLog --quiet) + + # Create or update CHANGELOG.md with Unreleased section + if [ -f CHANGELOG.md ]; then + # Check if Unreleased section exists + if grep -q "## \[Unreleased\]" CHANGELOG.md; then + # Replace existing Unreleased section + # Create temp file with new unreleased content + cat > CHANGELOG_NEW.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + # Append everything after the old Unreleased section (previous releases) + sed -n '/^## \[[0-9]/,$p' CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || true + mv CHANGELOG_NEW.md CHANGELOG.md + else + # No Unreleased section, add it at the top + cat > CHANGELOG_NEW.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + # Append existing content (skip any header) + tail -n +2 CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || true + mv CHANGELOG_NEW.md CHANGELOG.md + fi else - echo "needs_release=false" >> $GITHUB_OUTPUT - echo "Already at release version, skipping" + # Create new CHANGELOG.md + cat > CHANGELOG.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG.md fi - - name: Generate changelog - if: steps.check.outputs.needs_release == 'true' + - name: Check for changelog changes + id: changes run: | - ./gradlew printChangeLog --quiet > CHANGELOG_NEW.md - if [ -f CHANGELOG.md ]; then - # Prepend new changelog to existing - cat CHANGELOG_NEW.md CHANGELOG.md > CHANGELOG_COMBINED.md - mv CHANGELOG_COMBINED.md CHANGELOG.md + if git diff --quiet CHANGELOG.md 2>/dev/null; then + echo "changed=false" >> $GITHUB_OUTPUT else - mv CHANGELOG_NEW.md CHANGELOG.md + echo "changed=true" >> $GITHUB_OUTPUT fi - rm -f CHANGELOG_NEW.md - cat CHANGELOG.md - - name: Configure git - if: steps.check.outputs.needs_release == 'true' + - name: Commit changelog updates + if: steps.changes.outputs.changed == 'true' run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Create release - if: steps.check.outputs.needs_release == 'true' - run: | - # Add changelog changes git add CHANGELOG.md - - # Create release using git-semver-plugin - # This creates the release commit and tag - ./gradlew releaseVersion --no-commit - - # Commit changelog with release - git commit -m "chore(release): release version ${{ steps.check.outputs.release_version }}" - - # Create the tag - git tag "v${{ steps.check.outputs.release_version }}" - - - name: Push release - if: steps.check.outputs.needs_release == 'true' - run: | + git commit -m "chore(changelog): update unreleased changes" git push origin main - git push origin "v${{ steps.check.outputs.release_version }}" - - - name: Create GitHub Release - if: steps.check.outputs.needs_release == 'true' - uses: softprops/action-gh-release@v2 - with: - tag_name: "v${{ steps.check.outputs.release_version }}" - name: "Release ${{ steps.check.outputs.release_version }}" - body_path: CHANGELOG.md - draft: false - prerelease: false - name: Summary - if: steps.check.outputs.needs_release == 'true' run: | - echo "### Release Created" >> $GITHUB_STEP_SUMMARY + echo "### Snapshot Build Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "**Version:** ${{ steps.check.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY - echo "**Tag:** v${{ steps.check.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY + echo "**Changelog:** Updated with latest commits" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "The release-publish workflow will now publish Docker images." >> $GITHUB_STEP_SUMMARY + echo "To create a release, run the **Cut Release** workflow." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/cut-release.yml b/.github/workflows/cut-release.yml new file mode 100644 index 0000000..4b7f1f5 --- /dev/null +++ b/.github/workflows/cut-release.yml @@ -0,0 +1,232 @@ +# 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 +# +# http://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. + +# ============================================================================ +# CUT RELEASE WORKFLOW +# ============================================================================ +# +# PURPOSE: +# -------- +# Manually triggered workflow to create a new version release. +# Calculates version from conventional commits, creates tag, and publishes. +# +# WHEN TO USE: +# ------------ +# - When you're ready to create a new development release +# - After accumulating meaningful changes in main +# - This is for development releases, NOT official ASF releases +# +# WHAT IT DOES: +# ------------- +# 1. Calculates version from conventional commits since last tag +# 2. Moves "Unreleased" changelog entries to new version section +# 3. Creates version tag (e.g., v1.0.0) +# 4. Creates GitHub Release with changelog +# 5. Triggers build-and-publish.yml to publish Docker images +# +# FOR OFFICIAL ASF RELEASES: +# -------------------------- +# Use release-publish.yml instead, which requires: +# - RC tag (v1.0.0-rc1) +# - 72-hour ASF vote +# - Publishes to apache/solr-mcp namespace +# +# RELATED WORKFLOWS: +# ------------------ +# - auto-release.yml: Accumulates changelog on merge (no tags) +# - build-and-publish.yml: Publishes Docker images on tag +# - release-publish.yml: Official ASF releases (after vote) + +name: Cut Release + +on: + workflow_dispatch: + inputs: + version_override: + description: 'Override calculated version (leave empty to auto-calculate)' + required: false + type: string + dry_run: + description: 'Dry run (no tag/release created)' + required: false + type: boolean + default: false + +permissions: + contents: write + +env: + JAVA_VERSION: '25' + JAVA_DISTRIBUTION: 'temurin' + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Calculate version + id: version + run: | + if [ -n "${{ inputs.version_override }}" ]; then + VERSION="${{ inputs.version_override }}" + echo "Using override version: $VERSION" + else + # Get version from git-semver-plugin and remove SNAPSHOT suffix + SNAPSHOT_VERSION=$(./gradlew printVersion --quiet) + VERSION=$(echo "$SNAPSHOT_VERSION" | sed 's/-SNAPSHOT.*//') + echo "Calculated version: $VERSION (from $SNAPSHOT_VERSION)" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Check if tag already exists + id: check_tag + run: | + if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "::error::Tag v${{ steps.version.outputs.version }} already exists!" + exit 1 + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Generate release changelog + id: changelog + run: | + VERSION="${{ steps.version.outputs.version }}" + DATE=$(date +%Y-%m-%d) + + # Get changelog content from git-semver-plugin + CHANGELOG_CONTENT=$(./gradlew printChangeLog --quiet) + + # Save for GitHub Release body + echo "$CHANGELOG_CONTENT" > RELEASE_NOTES.md + + # Update CHANGELOG.md - move Unreleased to new version + if [ -f CHANGELOG.md ]; then + cat > CHANGELOG_NEW.md << HEADER + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + ## [$VERSION] - $DATE + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + # Append previous releases (everything after old Unreleased section) + sed -n '/^## \[[0-9]/,$p' CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || true + mv CHANGELOG_NEW.md CHANGELOG.md + else + cat > CHANGELOG.md << HEADER + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + ## [$VERSION] - $DATE + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG.md + fi + + - name: Show dry run results + if: inputs.dry_run + run: | + echo "### Dry Run Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** v${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Changelog:**" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat RELEASE_NOTES.md >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**CHANGELOG.md preview:**" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + head -50 CHANGELOG.md >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Configure git + if: "!inputs.dry_run" + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit changelog and create tag + if: "!inputs.dry_run" + run: | + VERSION="${{ steps.version.outputs.version }}" + + # Commit changelog update + git add CHANGELOG.md + git commit -m "chore(release): release version $VERSION" + + # Create annotated tag + git tag -a "v$VERSION" -m "Release version $VERSION" + + - name: Push changes and tag + if: "!inputs.dry_run" + run: | + git push origin main + git push origin "v${{ steps.version.outputs.version }}" + + - name: Create GitHub Release + if: "!inputs.dry_run" + uses: softprops/action-gh-release@v2 + with: + tag_name: "v${{ steps.version.outputs.version }}" + name: "Release ${{ steps.version.outputs.version }}" + body_path: RELEASE_NOTES.md + draft: false + prerelease: false + + - name: Summary + if: "!inputs.dry_run" + run: | + echo "### Release Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** v${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Next Steps:**" >> $GITHUB_STEP_SUMMARY + echo "- Docker images will be published automatically by build-and-publish.yml" >> $GITHUB_STEP_SUMMARY + echo "- For official ASF release, create RC tag and run release-publish.yml after vote" >> $GITHUB_STEP_SUMMARY diff --git a/build.gradle.kts b/build.gradle.kts index ff35805..05a8d2c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,16 +30,36 @@ plugins { } group = "org.apache.solr" -// Version is automatically derived from git tags and conventional commits -// Run ./gradlew printVersion to see the current version -// Run ./gradlew releaseVersion to create a release tag +// ============================================================================ +// Semantic Versioning Configuration (git-semver-plugin) +// ============================================================================ +// +// Version is automatically derived from git tags and conventional commits. +// The plugin analyzes commits since the last tag to determine the next version: +// - fix: commits β†’ patch bump (1.0.0 β†’ 1.0.1) +// - feat: commits β†’ minor bump (1.0.0 β†’ 1.1.0) +// - feat!: or BREAKING CHANGE β†’ major bump (1.0.0 β†’ 2.0.0) +// +// Commands: +// ./gradlew printVersion - Show current calculated version +// ./gradlew printChangeLog - Show changelog from commits +// +// Initial Version Setup: +// ---------------------- +// To start at version 1.0.0, create a baseline tag: +// git tag v0.0.0 -m "Initial version baseline" +// git push origin v0.0.0 +// +// Then any feat: commit will bump to 0.1.0, or use the cut-release workflow +// with version_override to set 1.0.0 explicitly for the first release. +// semver { - // Use "SNAPSHOT" suffix for non-release builds + // Use "SNAPSHOT" suffix for development builds defaultPreRelease = "SNAPSHOT" // Tag format: v1.0.0 releaseTagNameFormat = "v%s" - // Release commit message format + // Release commit message format (used by cut-release workflow) releaseCommitTextFormat = "chore(release): release version %s" } diff --git a/dev-docs/WORKFLOWS.md b/dev-docs/WORKFLOWS.md index e036019..260eae5 100644 --- a/dev-docs/WORKFLOWS.md +++ b/dev-docs/WORKFLOWS.md @@ -7,7 +7,8 @@ This guide explains when and how to use each GitHub Actions workflow in the proj | Workflow | Purpose | Trigger | Status | Use For | |------------------------------------------------|-----------------------|----------------------|------------|------------------------| | [build-and-publish.yml](#build-and-publishyml) | Development CI/CD | Automatic (push/PR) | βœ… Active | Daily development | -| [auto-release.yml](#auto-releaseyml) | Automated releases | Automatic (merge) | βœ… Active | Version tagging | +| [auto-release.yml](#auto-releaseyml) | Snapshot builds | Automatic (merge) | βœ… Active | Changelog accumulation | +| [cut-release.yml](#cut-releaseyml) | Cut new release | Manual | βœ… Active | Version tagging | | [release-publish.yml](#release-publishyml) | Official ASF releases | Manual (after vote) | βœ… Active | Production releases | | [nightly-build.yml](#nightly-buildyml) | Nightly builds | Scheduled (2 AM UTC) | βœ… Active | Latest unstable builds | | [atr-release-test.yml](#atr-release-testyml) | ATR testing | Manual (safe mode) | βœ… Ready | Testing ATR workflow | @@ -64,18 +65,18 @@ Use conventional commit prefixes to control version bumps: β”‚ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Develop β”‚ β”‚ Release β”‚ β”‚ Official β”‚ β”‚ Test β”‚ -β”‚ Code β”‚ β”‚ Version β”‚ β”‚ ASF Rel β”‚ β”‚ ATR β”‚ +β”‚ Develop β”‚ β”‚ Cut a β”‚ β”‚ Official β”‚ β”‚ Test β”‚ +β”‚ Code β”‚ β”‚ Release β”‚ β”‚ ASF Rel β”‚ β”‚ ATR β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚build-and- β”‚ β”‚auto- β”‚ β”‚ release- β”‚ β”‚atr- β”‚ +β”‚build-and- β”‚ β”‚cut- β”‚ β”‚ release- β”‚ β”‚atr- β”‚ β”‚publish.yml β”‚ β”‚release β”‚ β”‚ publish β”‚ β”‚release- β”‚ -β”‚ β”‚ β”‚ .yml β”‚ β”‚ .yml β”‚ β”‚test.yml β”‚ -β”‚βœ… Automatic β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ on PR β”‚ β”‚βœ… Auto on β”‚ β”‚βœ… Manual β”‚ β”‚βœ… Manual β”‚ -β”‚ β”‚ β”‚merge main β”‚ β”‚after vote β”‚ β”‚safe mode β”‚ +β”‚+ auto- β”‚ β”‚ .yml β”‚ β”‚ .yml β”‚ β”‚test.yml β”‚ +β”‚release.yml β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚βœ… Automatic β”‚ β”‚βœ… Manual β”‚ β”‚βœ… Manual β”‚ β”‚βœ… Manual β”‚ +β”‚ on merge β”‚ β”‚ trigger β”‚ β”‚after vote β”‚ β”‚safe mode β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό @@ -94,6 +95,75 @@ Use conventional commit prefixes to control version bumps: β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` +## Complete Release Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DEVELOPMENT CYCLE β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ PR Created β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ build-and-publish β”‚ Builds SNAPSHOT, runs tests β”‚ +β”‚ β”‚ (PR validation) β”‚ Publishes to personal/GHCR β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ PR Merged to main β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ auto-release.yml β”‚ Builds SNAPSHOT β”‚ +β”‚ β”‚ (Snapshot Build) β”‚ Updates CHANGELOG.md (Unreleased) β”‚ +β”‚ β”‚ β”‚ NO tag created β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ ... more PRs merged, changelog accumulates ... β”‚ +β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ DEVELOPMENT RELEASE β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Manual: "Cut Release" triggered β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ cut-release.yml β”‚ Calculates version (e.g., 1.1.0) β”‚ +β”‚ β”‚ β”‚ Moves Unreleased β†’ [1.1.0] in CHANGELOG β”‚ +β”‚ β”‚ β”‚ Creates tag v1.1.0 β”‚ +β”‚ β”‚ β”‚ Creates GitHub Release β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ build-and-publish β”‚ Triggered by v* tag β”‚ +β”‚ β”‚ (tag trigger) β”‚ Publishes Docker 1.1.0 to personal/GHCR β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ OFFICIAL ASF RELEASE β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ Manual: Create RC tag β”‚ +β”‚ β”‚ git tag v1.1.0-rc1 β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ 72-hour ASF Vote β”‚ Email to dev@solr.apache.org β”‚ +β”‚ β”‚ (3+ PMC +1 votes) β”‚ Wait for vote to pass β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ release-publish.yml β”‚ Manual trigger after vote β”‚ +β”‚ β”‚ (Official Release) β”‚ Publishes to apache/solr-mcp β”‚ +β”‚ β”‚ β”‚ Publishes to MCP Registry β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + --- ## Detailed Workflow Documentation @@ -175,18 +245,19 @@ gh workflow run build-and-publish.yml ### auto-release.yml -**Purpose**: Automated version tagging and changelog generation on merge to main +**Purpose**: Build SNAPSHOT artifacts and accumulate changelog entries on merge to main #### When to Use - βœ… Automatic on every merge to `main` -- βœ… Creates version tags based on conventional commits -- βœ… Generates and updates CHANGELOG.md -- βœ… Triggers Docker image publishing via build-and-publish.yml +- βœ… Builds and validates SNAPSHOT artifacts +- βœ… Accumulates changelog entries under "Unreleased" section +- βœ… Does NOT create version tags (use `cut-release.yml` for that) #### When NOT to Use - ❌ This is fully automatic - no manual intervention needed +- ❌ For creating releases (use `cut-release.yml`) - ❌ For official ASF releases (use `release-publish.yml` after vote) #### Triggers @@ -200,24 +271,10 @@ on: #### What It Does -1. **Calculates version** from git tags and conventional commits -2. **Generates changelog** from commit messages -3. **Creates release commit** with updated CHANGELOG.md -4. **Creates version tag** (e.g., `v1.0.0`) -5. **Pushes tag** which triggers `build-and-publish.yml` -6. **Creates GitHub Release** with changelog - -#### Version Calculation - -The [git-semver-plugin](https://github.com/jmongard/Git.SemVersioning.Gradle) analyzes commits since the last tag: - -``` -v1.0.0 (last tag) - β”‚ - β”œβ”€β”€ fix: bug fix β†’ 1.0.1 - β”œβ”€β”€ feat: new feature β†’ 1.1.0 - └── feat!: breaking change β†’ 2.0.0 -``` +1. **Builds SNAPSHOT** artifacts with calculated version +2. **Updates CHANGELOG.md** with new commits under "Unreleased" section +3. **Commits changelog** updates back to main +4. **Does NOT create tags** - changes accumulate until `cut-release.yml` is triggered #### How It Works @@ -226,56 +283,124 @@ PR Merged to main β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ 1. Calculate version β”‚ ./gradlew printVersion -β”‚ (from commits) β”‚ β†’ 1.1.0-SNAPSHOT +β”‚ 1. Build SNAPSHOT β”‚ ./gradlew build +β”‚ (validates code) β”‚ Version: 1.1.0-SNAPSHOT β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ 2. Generate changelogβ”‚ ./gradlew printChangeLog -β”‚ (from commits) β”‚ +β”‚ 2. Update changelog β”‚ Adds commits to "Unreleased" +β”‚ (accumulate) β”‚ section in CHANGELOG.md +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 3. Commit changelog β”‚ "chore(changelog): update +β”‚ (if changed) β”‚ unreleased changes" +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +No tag created - use cut-release.yml when ready to release +``` + +#### Skipping Changelog Updates + +Changelog and release commits are automatically skipped to prevent infinite loops: + +```yaml +if: "!startsWith(github.event.head_commit.message, 'chore(release):') && + !startsWith(github.event.head_commit.message, 'chore(changelog):')" +``` + +--- + +### cut-release.yml + +**Purpose**: Manually create a new version release with tag and finalized changelog + +#### When to Use + +- βœ… When you're ready to create a new development release +- βœ… After accumulating meaningful changes in main +- βœ… When you want to publish versioned Docker images + +#### When NOT to Use + +- ❌ For official ASF releases (use `release-publish.yml` after vote) +- ❌ For every merge (changes should accumulate first) + +#### Triggers + +```yaml +on: + workflow_dispatch: + inputs: + version_override: # Optional: override calculated version + dry_run: # Optional: preview without creating release +``` + +#### What It Does + +1. **Calculates version** from conventional commits since last tag +2. **Finalizes changelog** - moves "Unreleased" to new version section +3. **Creates version tag** (e.g., `v1.0.0`) +4. **Creates GitHub Release** with changelog +5. **Triggers build-and-publish.yml** to publish Docker images + +#### How It Works + +``` +Manual Trigger: "Cut Release" + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. Calculate version β”‚ feat: β†’ minor bump +β”‚ (from commits) β”‚ fix: β†’ patch bump β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ 3. Create release β”‚ git tag v1.1.0 -β”‚ commit + tag β”‚ git push --tags +β”‚ 2. Finalize changelogβ”‚ [Unreleased] β†’ [1.1.0] - 2024-01-15 +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ 4. GitHub Release β”‚ Created automatically -β”‚ with changelog β”‚ +β”‚ 3. Create tag β”‚ git tag v1.1.0 +β”‚ + GitHub Release β”‚ git push --tags β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ build-and-publish β”‚ Triggered by v* tag -β”‚ publishes Docker β”‚ +β”‚ publishes Docker β”‚ β†’ ghcr.io/*/solr-mcp:1.1.0 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -#### Example Commit Messages +#### How to Use + +**Via GitHub UI:** +1. Go to Actions β†’ Cut Release β†’ Run workflow +2. Optionally override version or enable dry run +3. Click "Run workflow" +**Via CLI:** ```bash -# These trigger version bumps: -git commit -m "feat: add new search filter" # Minor bump -git commit -m "fix: handle null pointer" # Patch bump -git commit -m "feat!: redesign API" # Major bump +# Auto-calculate version from commits +gh workflow run cut-release.yml -# These don't trigger version bumps: -git commit -m "docs: update README" -git commit -m "chore: update dependencies" -git commit -m "test: add unit tests" -``` +# Override version +gh workflow run cut-release.yml -f version_override=1.0.0 -#### Skipping Releases +# Dry run (preview only) +gh workflow run cut-release.yml -f dry_run=true +``` -Release commits are automatically skipped to prevent infinite loops: +#### Version Override -```yaml -if: "!startsWith(github.event.head_commit.message, 'chore(release):')" -``` +Use `version_override` for special cases: +- First release: `version_override=1.0.0` +- Forcing a major bump: `version_override=2.0.0` +- Specific version needed: `version_override=1.2.3` --- @@ -641,18 +766,17 @@ gh workflow run atr-release.yml \ ## Workflow Comparison Matrix -| Feature | build-and-publish | auto-release | release-publish | nightly-build | atr-release-test | atr-release | -|----------------------|-------------------|-----------------|-----------------|--------------------| -----------------|-------------| -| **Status** | βœ… Active | βœ… Active | βœ… Active | βœ… Active | βœ… Ready | ⚠️ Blocked | -| **Trigger** | Automatic | Auto (merge) | Manual | Scheduled | Manual | Manual | -| **Creates Tags** | ❌ No | βœ… Yes | ❌ No | ❌ No | ❌ No | ❌ No | -| **Changelog** | ❌ No | βœ… Yes | ❌ No | ❌ No | ❌ No | ❌ No | -| **Docker Namespace** | Personal/GHCR | N/A | `apache/*` | `apache/*-nightly` | Test | `apache/*` | -| **MCP Registry** | ❌ No | ❌ No | βœ… Yes | ❌ No | ❌ No | βœ… Yes | -| **ASF Vote** | ❌ Not required | ❌ Not required | βœ… Required | ❌ Not required | ❌ Not required | βœ… Required | -| **Signing** | ❌ No | ❌ No | ⚠️ Manual | ❌ No | ⚠️ Simulated | βœ… Automated | -| **Production Ready** | ❌ No | βœ… Yes | βœ… Yes | ❌ No | ❌ No | ⚠️ Future | -| **Can Test Now** | βœ… Yes | βœ… Yes | βœ… Yes | βœ… Yes | βœ… Yes | ❌ No | +| Feature | build-and-publish | auto-release | cut-release | release-publish | nightly-build | atr-release | +|----------------------|-------------------|-----------------|-----------------|-----------------|--------------------| ------------| +| **Status** | βœ… Active | βœ… Active | βœ… Active | βœ… Active | βœ… Active | ⚠️ Blocked | +| **Trigger** | Automatic | Auto (merge) | Manual | Manual | Scheduled | Manual | +| **Creates Tags** | ❌ No | ❌ No | βœ… Yes | ❌ No | ❌ No | ❌ No | +| **Changelog** | ❌ No | βœ… Accumulates | βœ… Finalizes | ❌ No | ❌ No | ❌ No | +| **Docker Namespace** | Personal/GHCR | N/A | Personal/GHCR | `apache/*` | `apache/*-nightly` | `apache/*` | +| **MCP Registry** | ❌ No | ❌ No | ❌ No | βœ… Yes | ❌ No | βœ… Yes | +| **ASF Vote** | ❌ Not required | ❌ Not required | ❌ Not required | βœ… Required | ❌ Not required | βœ… Required | +| **Signing** | ❌ No | ❌ No | ❌ No | ⚠️ Manual | ❌ No | βœ… Automated | +| **Official Release** | ❌ No | ❌ No | ❌ No | βœ… Yes | ❌ No | βœ… Yes | --- @@ -668,30 +792,35 @@ git merge feature-branch # e.g., "feat: add new search filter" git push origin main # 2. auto-release.yml automatically: -# - Calculates version (e.g., 1.1.0) -# - Generates CHANGELOG.md -# - Creates tag v1.1.0 -# - Creates GitHub Release +# - Builds SNAPSHOT artifacts +# - Updates CHANGELOG.md (Unreleased section) +# - NO tag created yet # 3. build-and-publish.yml automatically: -# - Builds Docker image -# - Publishes to ghcr.io/apache/solr-mcp:1.1.0 +# - Builds Docker image with SNAPSHOT version +# - Publishes to ghcr.io/*/solr-mcp:X.Y.Z-SNAPSHOT-SHA ``` ### Scenario 2: I want to create a version release -**Use**: Automatic via `auto-release.yml` +**Use**: `cut-release.yml` (manual trigger) ```bash -# Just merge PRs with conventional commits - releases happen automatically! +# 1. Ensure changes have accumulated in main +./gradlew printVersion # Check calculated version +./gradlew printChangeLog # Preview changelog -# Example commits that trigger releases: -git commit -m "feat: add faceted search" # β†’ Minor version bump -git commit -m "fix: handle null pointer" # β†’ Patch version bump -git commit -m "feat!: new query API" # β†’ Major version bump +# 2. Trigger the Cut Release workflow +gh workflow run cut-release.yml -# Check what version will be released: -./gradlew printVersion +# Or with version override for first release: +gh workflow run cut-release.yml -f version_override=1.0.0 + +# 3. Workflow automatically: +# - Creates tag v1.0.0 +# - Finalizes CHANGELOG.md +# - Creates GitHub Release +# - Triggers Docker publish to personal/GHCR ``` ### Scenario 3: I want to create an official ASF release @@ -815,9 +944,11 @@ gh secret set ASF_USERNAME --body "your-asf-id" # Semantic versioning commands (git-semver-plugin) ./gradlew printVersion # Show current calculated version ./gradlew printChangeLog # Show changelog from commits -./gradlew releaseVersion # Create release commit + tag # Trigger workflows manually +gh workflow run cut-release.yml # Cut a new release +gh workflow run cut-release.yml -f version_override=1.0.0 # First release +gh workflow run cut-release.yml -f dry_run=true # Preview only gh workflow run build-and-publish.yml gh workflow run release-publish.yml -f release_version=1.0.0 -f release_candidate=rc1 gh workflow run nightly-build.yml From 6a905bf375543e78579fa6f4c182d94ed7eec437 Mon Sep 17 00:00:00 2001 From: adityamparikh Date: Wed, 7 Jan 2026 12:18:42 -0500 Subject: [PATCH 3/3] docs: clarify PR behavior - JAR artifacts only, no Docker images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update WORKFLOWS.md to clearly document that pull requests: - Build and test the project - Upload JAR artifacts (downloadable from GitHub Actions) - Upload test results and coverage reports - Do NOT publish Docker images (publish job is skipped) This clarifies the distinction between PR validation (artifacts only) and merge/tag triggers (full Docker publishing). πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- dev-docs/WORKFLOWS.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/dev-docs/WORKFLOWS.md b/dev-docs/WORKFLOWS.md index 260eae5..f9429cc 100644 --- a/dev-docs/WORKFLOWS.md +++ b/dev-docs/WORKFLOWS.md @@ -106,8 +106,8 @@ Use conventional commit prefixes to control version bumps: β”‚ β”‚ β”‚ β”‚ β–Ό β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ build-and-publish β”‚ Builds SNAPSHOT, runs tests β”‚ -β”‚ β”‚ (PR validation) β”‚ Publishes to personal/GHCR β”‚ +β”‚ β”‚ build-and-publish β”‚ Builds project, runs tests β”‚ +β”‚ β”‚ (PR validation) β”‚ Uploads JAR artifacts (NO Docker images) β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ PR Merged to main β”‚ @@ -197,9 +197,20 @@ on: #### What It Does +**On Pull Requests** (build + test only, NO publishing): 1. **Builds** the project with Gradle 2. **Runs tests** and generates coverage reports -3. **Publishes Docker images** to: +3. **Uploads artifacts** to GitHub Actions (downloadable): + - JAR files (`solr-mcp-*.jar`) + - Test results + - Coverage reports +4. ❌ **NO Docker images** are published for PRs + +**On Push to Main / Tags** (full CI/CD): +1. **Builds** the project with Gradle +2. **Runs tests** and generates coverage reports +3. **Uploads artifacts** (same as PRs) +4. **Publishes Docker images** to: - GitHub Container Registry: `ghcr.io/OWNER/solr-mcp:VERSION-SHA` - Docker Hub: `DOCKERHUB_USERNAME/solr-mcp:VERSION-SHA` (if secrets configured)